Skip to content

Commit 0e932ca

Browse files
authored
Improve test reliability (#488)
* Improve test reliability Add tags to allow running only some of the tests - `mix test --only fixture` to run only the tests that use our custom in_fixture helper - `mix test --exclude slow` to exclude extra slow tests (primarily dialyzer) - Fix a race-condition that showed up in some debugger tests - Without this fix (which admittedly is hacky) the "notifies about process exit" debugger test would fail about 1/4 of the time. - Capture output and logs from debugger tests - Otherwise that output would make it more difficult to read the test output - Rename the error raised in debugger tests to make it more obvious that these are test-specific errors and not a generic ArgumentError (which could be raised for multiple reasons) * Increase timeout for build diagnostics test * Use start_supervised! to ensure that processes finish before next test Intent is to fix this test error: 1) test put_chars mfa mfa binary (ElixirLS.Utils.OutputDeviceTest) Error: test/output_device_test.exs:175 ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.484.0>}} stacktrace: test/output_device_test.exs:72: ElixirLS.Utils.OutputDeviceTest.__ex_unit_setup_0/1 test/output_device_test.exs:1: ElixirLS.Utils.OutputDeviceTest.__ex_unit__/2 https://github.com/elixir-lsp/elixir-ls/pull/488/checks?check_run_id=1899187694
1 parent ff4029f commit 0e932ca

File tree

11 files changed

+113
-21
lines changed

11 files changed

+113
-21
lines changed

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,10 @@ defmodule ElixirLS.Debugger.Server do
835835
end
836836

837837
defp launch_task(task, args) do
838+
# This fixes a race condition in the tests and likely improves reliability when using the
839+
# debugger as well.
840+
Process.sleep(100)
841+
838842
Mix.Task.run(task, args)
839843

840844
# Starting from Elixir 1.9 Mix.Task.run will return so we need to sleep our

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ defmodule ElixirLS.Debugger.ServerTest do
8989
end
9090
end
9191

92+
@tag :fixture
9293
test "basic debugging", %{server: server} do
9394
in_fixture(__DIR__, "mix_project", fn ->
9495
Server.receive_packet(server, initialize_req(1, %{}))
@@ -100,6 +101,7 @@ defmodule ElixirLS.Debugger.ServerTest do
100101
"request" => "launch",
101102
"type" => "mix_task",
102103
"task" => "test",
104+
"taskArgs" => ["--only", "quadruple"],
103105
"projectDir" => File.cwd!()
104106
})
105107
)
@@ -132,7 +134,8 @@ defmodule ElixirLS.Debugger.ServerTest do
132134
"allThreadsStopped" => false,
133135
"reason" => "breakpoint",
134136
"threadId" => thread_id
135-
})
137+
}),
138+
5_000
136139

137140
Server.receive_packet(server, stacktrace_req(7, thread_id))
138141

@@ -191,6 +194,7 @@ defmodule ElixirLS.Debugger.ServerTest do
191194
end)
192195
end
193196

197+
@tag :fixture
194198
test "handles invalid requests", %{server: server} do
195199
in_fixture(__DIR__, "mix_project", fn ->
196200
Server.receive_packet(server, initialize_req(1, %{}))
@@ -234,7 +238,8 @@ defmodule ElixirLS.Debugger.ServerTest do
234238
"allThreadsStopped" => false,
235239
"reason" => "breakpoint",
236240
"threadId" => thread_id
237-
})
241+
}),
242+
1_000
238243

239244
Server.receive_packet(server, stacktrace_req(7, "not existing"))
240245

@@ -341,6 +346,7 @@ defmodule ElixirLS.Debugger.ServerTest do
341346
end)
342347
end
343348

349+
@tag :fixture
344350
test "notifies about process exit", %{server: server} do
345351
in_fixture(__DIR__, "mix_project", fn ->
346352
Server.receive_packet(server, initialize_req(1, %{}))
@@ -377,7 +383,7 @@ defmodule ElixirLS.Debugger.ServerTest do
377383
assert_receive(response(_, 5, "configurationDone", %{}))
378384

379385
Server.receive_packet(server, request(6, "threads", %{}))
380-
assert_receive(response(_, 6, "threads", %{"threads" => threads}))
386+
assert_receive(response(_, 6, "threads", %{"threads" => threads}), 1_000)
381387
# ensure thread ids are unique
382388
thread_ids = Enum.map(threads, & &1["id"])
383389
assert Enum.count(Enum.uniq(thread_ids)) == Enum.count(thread_ids)
@@ -389,14 +395,21 @@ defmodule ElixirLS.Debugger.ServerTest do
389395
}),
390396
500
391397

392-
assert_receive event(_, "thread", %{
393-
"reason" => "exited",
394-
"threadId" => ^thread_id
395-
}),
396-
5000
398+
{log, stderr} =
399+
capture_log_and_io(:standard_error, fn ->
400+
assert_receive event(_, "thread", %{
401+
"reason" => "exited",
402+
"threadId" => ^thread_id
403+
}),
404+
5000
405+
end)
406+
407+
assert log =~ "Fixture MixProject expected error"
408+
assert stderr =~ "Fixture MixProject expected error"
397409
end)
398410
end
399411

412+
@tag :fixture
400413
test "notifies about mix task exit", %{server: server} do
401414
in_fixture(__DIR__, "mix_project", fn ->
402415
Server.receive_packet(server, initialize_req(1, %{}))
@@ -444,11 +457,17 @@ defmodule ElixirLS.Debugger.ServerTest do
444457
}),
445458
5000
446459

447-
assert_receive event(_, "thread", %{
448-
"reason" => "exited",
449-
"threadId" => ^thread_id
450-
}),
451-
5000
460+
{log, io} =
461+
capture_log_and_io(:stderr, fn ->
462+
assert_receive event(_, "thread", %{
463+
"reason" => "exited",
464+
"threadId" => ^thread_id
465+
}),
466+
5000
467+
end)
468+
469+
assert log =~ "Fixture MixProject raise for exit_self/0"
470+
assert io =~ "Fixture MixProject raise for exit_self/0"
452471

453472
assert_receive event(_, "exited", %{
454473
"exitCode" => 1
@@ -460,6 +479,7 @@ defmodule ElixirLS.Debugger.ServerTest do
460479
end)
461480
end
462481

482+
@tag :fixture
463483
test "sets breakpoints in erlang modules", %{server: server} do
464484
in_fixture(__DIR__, "mix_project", fn ->
465485
Server.receive_packet(server, initialize_req(1, %{}))

apps/elixir_ls_debugger/test/fixtures/mix_project/lib/mix_project.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule MixProject do
1111
Task.start(fn ->
1212
Task.start_link(fn ->
1313
Process.sleep(1000)
14-
raise ArgumentError
14+
raise "Fixture MixProject expected error"
1515
end)
1616

1717
Process.sleep(:infinity)
@@ -23,7 +23,7 @@ defmodule MixProject do
2323
def exit_self do
2424
Task.start_link(fn ->
2525
Process.sleep(1000)
26-
raise ArgumentError
26+
raise "Fixture MixProject raise for exit_self/0"
2727
end)
2828

2929
Process.sleep(:infinity)

apps/elixir_ls_debugger/test/fixtures/mix_project/test/mix_project_fixturetest.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ defmodule MixProjectTest do
22
use ExUnit.Case
33
doctest MixProject
44

5+
@tag :double
56
test "double" do
67
assert MixProject.double(2) == 4
78
end
89

10+
@tag :quadruple
911
test "quadruple" do
1012
assert MixProject.quadruple(2) == 8
1113
end

apps/elixir_ls_utils/lib/output_device.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ defmodule ElixirLS.Utils.OutputDevice do
1818
Task.start_link(fn -> loop({device, output_fn}) end)
1919
end
2020

21+
def child_spec(arguments) do
22+
%{
23+
id: __MODULE__,
24+
start: {__MODULE__, :start_link, arguments},
25+
type: :worker,
26+
restart: :permanent,
27+
shutdown: 500
28+
}
29+
end
30+
2131
def get_opts, do: @opts
2232

2333
## Implementation

apps/elixir_ls_utils/test/output_device_test.exs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,16 @@ defmodule ElixirLS.Utils.OutputDeviceTest do
6868
end
6969

7070
setup do
71-
{:ok, fake_user} = FakeOutput.start_link([])
72-
{:ok, fake_wire_protocol} = FakeWireProtocol.start_link([])
73-
{:ok, output_device} = OutputDevice.start_link(fake_user, &FakeWireProtocol.send/1)
74-
{:ok, output_device_error} = OutputDevice.start_link(fake_user, fn _ -> {:error, :emfile} end)
71+
fake_user = start_supervised!({FakeOutput, []})
72+
fake_wire_protocol = start_supervised!({FakeWireProtocol, []})
73+
74+
output_device =
75+
start_supervised!({OutputDevice, [fake_user, &FakeWireProtocol.send/1]}, id: :output_device)
76+
77+
output_device_error =
78+
start_supervised!({OutputDevice, [fake_user, fn _ -> {:error, :emfile} end]},
79+
id: :output_device_error
80+
)
7581

7682
{:ok,
7783
%{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule ElixirLS.Utils.PlaceholderTest do
2+
use ExUnit.Case, async: true
3+
4+
@tag :fixture
5+
test "pretend fixture" do
6+
# This test is included to prevent the following error:
7+
# > The --only option was given to "mix test" but no test was executed
8+
:ok
9+
end
10+
end

apps/elixir_ls_utils/test/support/mix_test.case.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ defmodule ElixirLS.Utils.MixTest.Case do
7474
previous = :code.all_loaded()
7575
project_stack = clear_project_stack!()
7676

77+
ExUnit.CaptureLog.capture_log(fn -> Application.stop(:mix) end)
78+
Application.start(:mix)
79+
7780
try do
7881
File.cd!(dest, function)
7982
after
@@ -148,4 +151,19 @@ defmodule ElixirLS.Utils.MixTest.Case do
148151

149152
module.clear_cache()
150153
end
154+
155+
def capture_log_and_io(device, fun) when is_function(fun, 0) do
156+
# Logger gets stopped during some tests so restart it to be able to capture logs (and keept the
157+
# test output clean)
158+
Application.ensure_started(:logger)
159+
160+
log =
161+
ExUnit.CaptureLog.capture_log(fn ->
162+
io = ExUnit.CaptureIO.capture_io(device, fun)
163+
send(self(), {:block_result, io})
164+
end)
165+
166+
assert_received {:block_result, io_result}
167+
{log, io_result}
168+
end
151169
end

apps/language_server/test/dialyzer_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
1919
{:ok, %{server: server}}
2020
end
2121

22+
@tag slow: true, fixture: true
2223
test "reports diagnostics then clears them once problems are fixed", %{server: server} do
2324
in_fixture(__DIR__, "dialyzer", fn ->
2425
file_a = SourceFile.path_to_uri(Path.absname("lib/a.ex"))
@@ -91,6 +92,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
9192
end)
9293
end
9394

95+
@tag slow: true, fixture: true
9496
test "only analyzes the changed files", %{server: server} do
9597
in_fixture(__DIR__, "dialyzer", fn ->
9698
file_c = SourceFile.path_to_uri(Path.absname("lib/c.ex"))
@@ -151,6 +153,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
151153
end)
152154
end
153155

156+
@tag slow: true, fixture: true
154157
test "reports dialyxir_long formatted error", %{server: server} do
155158
in_fixture(__DIR__, "dialyzer", fn ->
156159
file_a = SourceFile.path_to_uri(Path.absname("lib/a.ex"))
@@ -204,6 +207,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
204207
end)
205208
end
206209

210+
@tag slow: true, fixture: true
207211
test "reports dialyxir_short formatted error", %{server: server} do
208212
in_fixture(__DIR__, "dialyzer", fn ->
209213
file_a = SourceFile.path_to_uri(Path.absname("lib/a.ex"))
@@ -248,6 +252,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
248252
end)
249253
end
250254

255+
@tag slow: true, fixture: true
251256
test "reports dialyzer_formatted error", %{server: server} do
252257
in_fixture(__DIR__, "dialyzer", fn ->
253258
file_a = SourceFile.path_to_uri(Path.absname("lib/a.ex"))
@@ -293,6 +298,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
293298
end)
294299
end
295300

301+
@tag slow: true, fixture: true
296302
test "reports dialyxir_short error in umbrella", %{server: server} do
297303
in_fixture(__DIR__, "umbrella_dialyzer", fn ->
298304
file_a = SourceFile.path_to_uri(Path.absname("apps/app1/lib/app1.ex"))
@@ -363,6 +369,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
363369
end)
364370
end
365371

372+
@tag slow: true, fixture: true
366373
test "protocol rebuild does not trigger consolidation warnings", %{server: server} do
367374
in_fixture(__DIR__, "protocols", fn ->
368375
root_uri = SourceFile.path_to_uri(File.cwd!())

apps/language_server/test/providers/formatting_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
55
alias ElixirLS.LanguageServer.SourceFile
66
alias ElixirLS.LanguageServer.Test.FixtureHelpers
77

8+
@tag :fixture
89
test "Formats a file" do
910
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
1011
path = "lib/file.ex"
@@ -57,6 +58,7 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
5758
defp assert_position_type(%{"character" => ch, "line" => line}),
5859
do: is_integer(ch) and is_integer(line)
5960

61+
@tag :fixture
6062
test "returns an error when formatting a file with a syntax error" do
6163
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
6264
path = "lib/file.ex"
@@ -85,6 +87,7 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
8587
end)
8688
end
8789

90+
@tag :fixture
8891
test "Proper utf-16 format: emoji 😀" do
8992
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
9093
path = "lib/file.ex"
@@ -123,6 +126,7 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
123126
end)
124127
end
125128

129+
@tag :fixture
126130
test "Proper utf-16 format: emoji 🏳️‍🌈" do
127131
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
128132
path = "lib/file.ex"
@@ -161,6 +165,7 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
161165
end)
162166
end
163167

168+
@tag :fixture
164169
test "Proper utf-16 format: zalgo" do
165170
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
166171
path = "lib/file.ex"

0 commit comments

Comments
 (0)