Skip to content

Commit

Permalink
fix(test runner): Use ExUnit testPaths and testPattern (elixir-lsp#500)
Browse files Browse the repository at this point in the history
* fix(test runner): Use ExUnit testPaths and testPattern

* Handle bad URI format

* Add tests for test_paths and test_pattern

* Make all fixture apps unique

* Rename umbrella app to match filesystem

* wait for compilation after tests

* wait for compilation before code lens request
  • Loading branch information
Étienne Lévesque authored and vanjabucic committed Jul 5, 2021
1 parent 3e47cf6 commit e0ccca3
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens.Test do
runnable_functions = [{:test, 3}, {:test, 2}]

for func <- runnable_functions,
{line, _col} <- calls_to(calls_list, func),
is_test_module?(lines_to_env_list, line) do
{line, _col} <- calls_to(calls_list, func) do
{_line, %{scope_id: scope_id}} =
Enum.find(lines_to_env_list, fn {env_line, _env} -> env_line == line end)

Expand All @@ -101,8 +100,7 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens.Test do
defp find_describe_blocks(lines_to_env_list, calls_list, source_lines) do
lines_to_env_list_length = length(lines_to_env_list)

for {line, _col} <- calls_to(calls_list, {:describe, 2}),
is_test_module?(lines_to_env_list, line) do
for {line, _col} <- calls_to(calls_list, {:describe, 2}) do
DescribeBlock.find_block_info(
line,
lines_to_env_list,
Expand All @@ -126,23 +124,9 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens.Test do
defp get_test_modules(lines_to_env) do
lines_to_env
|> Enum.group_by(fn {_line, env} -> env.module end)
|> Enum.filter(fn {_module, module_lines_to_env} -> is_test_module?(module_lines_to_env) end)
|> Enum.map(fn {module, [{line, _env} | _rest]} -> {module, line} end)
end

defp is_test_module?(lines_to_env), do: is_test_module?(lines_to_env, :infinity)

defp is_test_module?(lines_to_env, line) when is_list(lines_to_env) do
lines_to_env
|> Enum.max_by(fn
{env_line, _env} when env_line < line -> env_line
_ -> -1
end)
|> elem(1)
|> Map.get(:imports)
|> Enum.any?(fn module -> module == ExUnit.Case end)
end

defp calls_to(calls_list, {function, arity}) do
for call_info <- calls_list,
call_info.func == function and call_info.arity === arity do
Expand Down
66 changes: 62 additions & 4 deletions apps/language_server/lib/language_server/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -806,13 +806,71 @@ defmodule ElixirLS.LanguageServer.Server do
end

defp get_test_code_lenses(state, uri, source_file) do
if state.settings["enableTestLenses"] == true do
CodeLens.test_code_lens(uri, source_file.text, state.project_dir)
else
{:ok, []}
get_test_code_lenses(
state,
uri,
source_file,
state.settings["enableTestLenses"] || false,
Mix.Project.umbrella?()
)
end

defp get_test_code_lenses(_state, _uri, _source_file, false, _), do: {:ok, []}

defp get_test_code_lenses(state, uri, source_file, true = _enabled, true = _umbrella) do
file_path = SourceFile.path_from_uri(uri)

Mix.Project.apps_paths()
|> Enum.find(fn {_app, app_path} -> String.contains?(file_path, app_path) end)
|> case do
nil ->
{:ok, []}

{app, app_path} ->
if is_test_file?(file_path, state, app, app_path) do
CodeLens.test_code_lens(uri, source_file.text, "#{state.project_dir}/#{app_path}")
else
{:ok, []}
end
end
end

defp get_test_code_lenses(state, uri, source_file, true = _enabled, false = _umbrella) do
try do
file_path = SourceFile.path_from_uri(uri)

if is_test_file?(file_path) do
CodeLens.test_code_lens(uri, source_file.text, state.project_dir)
else
{:ok, []}
end
rescue
_ in ArgumentError -> {:ok, []}
end
end

defp is_test_file?(file_path, state, app, app_path) do
app_name = Atom.to_string(app)

test_paths =
(get_in(state.settings, ["testPaths", app_name]) || ["test"])
|> Enum.map(fn path -> Path.join([state.project_dir, app_path, path]) end)

test_pattern = get_in(state.settings, ["testPattern", app_name]) || "*_test.exs"

Mix.Utils.extract_files(test_paths, test_pattern)
|> Enum.any?(fn path -> String.ends_with?(file_path, path) end)
end

defp is_test_file?(file_path) do
test_paths = Mix.Project.config()[:test_paths] || ["test"]
test_pattern = Mix.Project.config()[:test_pattern] || "*_test.exs"

Mix.Utils.extract_files(test_paths, test_pattern)
|> Enum.map(&Path.absname/1)
|> Enum.any?(&(&1 == file_path))
end

# Build

defp trigger_build(state) do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule TestCodeLensCustomPathsAndPatternTest do
use ExUnit.Case

test "fixture test" do
assert true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule TestCodeLensCustomPathsAndPattern.MixProject do
use Mix.Project

def project do
[
app: :test_code_lens_custom_paths_and_pattern,
version: "0.1.0",
test_paths: ["custom_path"],
test_pattern: "*_custom_test.exs"
]
end

def application, do: []
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule UmbrellaTestCodeLensCustomPathAndPatternTest do
use ExUnit.Case

test "fixture test" do
assert true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule App1.Mixfile do
use Mix.Project

def project do
[
app: :app1,
version: "0.1.0"
]
end

def application do
[]
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule UmbrellaTestCodeLensCustomPathAndPattern.Mixfile do
use Mix.Project

def project do
[apps_path: "apps"]
end
end
13 changes: 0 additions & 13 deletions apps/language_server/test/providers/code_lens/test_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,6 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens.TestTest do
]
end

test "does not return lenses for modules that don't import ExUnit.case" do
uri = "file:///project/file.ex"

text = """
defmodule MyModule do
end
"""

{:ok, lenses} = CodeLens.Test.code_lens(uri, text, @project_dir)

assert lenses == []
end

test "returns lenses for all describe blocks" do
uri = "file:///project/file.ex"

Expand Down
143 changes: 143 additions & 0 deletions apps/language_server/test/server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,149 @@ defmodule ElixirLS.LanguageServer.ServerTest do
end)
end

@tag :fixture
test "returns code lenses for runnable tests with custom test paths and test pattern", %{
server: server
} do
in_fixture(__DIR__, "test_code_lens_custom_paths_and_pattern", fn ->
file_path = "custom_path/fixture_custom_test.exs"
file_uri = SourceFile.path_to_uri(file_path)
file_absolute_path = SourceFile.path_from_uri(file_uri)
text = File.read!(file_path)
project_dir = SourceFile.path_from_uri(root_uri())

initialize(server)

Server.receive_packet(
server,
did_change_configuration(%{"elixirLS" => %{"enableTestLenses" => true}})
)

Server.receive_packet(server, did_open(file_uri, "elixir", 1, text))

wait_until_compiled(server)

Server.receive_packet(
server,
code_lens_req(4, file_uri)
)

resp = assert_receive(%{"id" => 4}, 5000)

assert response(4, [
%{
"command" => %{
"arguments" => [
%{
"filePath" => ^file_absolute_path,
"testName" => "fixture test",
"projectDir" => ^project_dir
}
],
"command" => "elixir.lens.test.run",
"title" => "Run test"
},
"range" => %{
"end" => %{"character" => 0, "line" => 3},
"start" => %{"character" => 0, "line" => 3}
}
},
%{
"command" => %{
"arguments" => [
%{
"filePath" => ^file_absolute_path,
"module" => "Elixir.TestCodeLensCustomPathsAndPatternTest",
"projectDir" => ^project_dir
}
],
"command" => "elixir.lens.test.run",
"title" => "Run tests in module"
},
"range" => %{
"end" => %{"character" => 0, "line" => 0},
"start" => %{"character" => 0, "line" => 0}
}
}
]) = resp
end)
end

@tag :fixture
test "returns code lenses for runnable tests with custom test paths and test pattern in umbrella apps",
%{
server: server
} do
in_fixture(__DIR__, "umbrella_test_code_lens_custom_path_and_pattern", fn ->
file_path = "apps/app1/custom_path/fixture_custom_test.exs"
file_uri = SourceFile.path_to_uri(file_path)
file_absolute_path = SourceFile.path_from_uri(file_uri)
text = File.read!(file_path)
project_dir = SourceFile.path_from_uri("#{root_uri()}/apps/app1")

initialize(server)

Server.receive_packet(
server,
did_change_configuration(%{
"elixirLS" => %{
"enableTestLenses" => true,
"testPaths" => %{"app1" => ["custom_path"]},
"testPattern" => %{"app1" => "*_custom_test.exs"}
}
})
)

Server.receive_packet(server, did_open(file_uri, "elixir", 1, text))

wait_until_compiled(server)

Server.receive_packet(
server,
code_lens_req(4, file_uri)
)

resp = assert_receive(%{"id" => 4}, 5000)

assert response(4, [
%{
"command" => %{
"arguments" => [
%{
"filePath" => ^file_absolute_path,
"testName" => "fixture test",
"projectDir" => ^project_dir
}
],
"command" => "elixir.lens.test.run",
"title" => "Run test"
},
"range" => %{
"end" => %{"character" => 0, "line" => 3},
"start" => %{"character" => 0, "line" => 3}
}
},
%{
"command" => %{
"arguments" => [
%{
"filePath" => ^file_absolute_path,
"module" => "Elixir.UmbrellaTestCodeLensCustomPathAndPatternTest",
"projectDir" => ^project_dir
}
],
"command" => "elixir.lens.test.run",
"title" => "Run tests in module"
},
"range" => %{
"end" => %{"character" => 0, "line" => 0},
"start" => %{"character" => 0, "line" => 0}
}
}
]) = resp
end)
end

defp with_new_server(func) do
server = start_supervised!({Server, nil})
packet_capture = start_supervised!({PacketCapture, self()})
Expand Down

0 comments on commit e0ccca3

Please sign in to comment.