Skip to content

Commit 35c64b9

Browse files
committed
fix(runtime): don't call on_initialized after init
fix(runtime): don't crash if compile is called before ready test(runtime): rgroup related tests in describe blocks
1 parent 2d369d9 commit 35c64b9

File tree

2 files changed

+154
-111
lines changed

2 files changed

+154
-111
lines changed

lib/next_ls/runtime.ex

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,11 @@ defmodule NextLS.Runtime do
136136
|> Path.join("monkey/_next_ls_private_compiler.ex")
137137
|> then(&:rpc.call(node, Code, :compile_file, [&1]))
138138
|> tap(fn
139-
{:badrpc, error} -> NextLS.Logger.error(logger, "Bad RPC call: #{inspect(error)}")
140-
_ -> :ok
139+
{:badrpc, error} ->
140+
NextLS.Logger.error(logger, "Bad RPC call to node #{node}: #{inspect(error)}")
141+
142+
_ ->
143+
:ok
141144
end)
142145

143146
:rpc.call(node, Code, :put_compiler_option, [:parser_options, [columns: true, token_metadata: true]])
@@ -190,7 +193,7 @@ defmodule NextLS.Runtime do
190193
Task.Supervisor.async_nolink(state.task_supervisor, fn ->
191194
case :rpc.call(node, :_next_ls_private_compiler, :compile, []) do
192195
{:badrpc, error} ->
193-
NextLS.Logger.error(state.logger, "Bad RPC call: #{inspect(error)}")
196+
NextLS.Logger.error(state.logger, "Bad RPC call to node #{node}: #{inspect(error)}")
194197
[]
195198

196199
{_, diagnostics} when is_list(diagnostics) ->
@@ -209,6 +212,10 @@ defmodule NextLS.Runtime do
209212
{:noreply, %{state | compiler_ref: %{task.ref => from}}}
210213
end
211214

215+
def handle_call(:compile, _from, state) do
216+
{:reply, {:error, :not_ready}, state}
217+
end
218+
212219
@impl GenServer
213220
def handle_info({ref, errors}, %{compiler_ref: compiler_ref} = state) when is_map_key(compiler_ref, ref) do
214221
Process.demonitor(ref, [:flush])
@@ -225,7 +232,11 @@ defmodule NextLS.Runtime do
225232

226233
def handle_info({:DOWN, _, :port, port, reason}, %{port: port} = state) do
227234
error = {:port_down, reason}
228-
state.on_initialized.({:error, error})
235+
236+
unless is_map_key(state, :node) do
237+
state.on_initialized.({:error, error})
238+
end
239+
229240
{:stop, {:shutdown, error}, state}
230241
end
231242

test/next_ls/runtime_test.exs

Lines changed: 139 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -42,37 +42,122 @@ defmodule NextLs.RuntimeTest do
4242
[logger: logger, cwd: Path.absname(tmp_dir), on_init: on_init]
4343
end
4444

45-
test "returns the response in an ok tuple", %{logger: logger, cwd: cwd, on_init: on_init} do
46-
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
47-
tvisor = start_supervised!(Task.Supervisor)
45+
describe "call/2" do
46+
test "responds with an ok tuple", %{logger: logger, cwd: cwd, on_init: on_init} do
47+
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
48+
tvisor = start_supervised!(Task.Supervisor)
49+
50+
pid =
51+
start_supervised!(
52+
{Runtime,
53+
name: "my_proj",
54+
on_initialized: on_init,
55+
task_supervisor: tvisor,
56+
working_dir: cwd,
57+
uri: "file://#{cwd}",
58+
parent: self(),
59+
logger: logger,
60+
db: :some_db,
61+
registry: RuntimeTest.Registry}
62+
)
63+
64+
Process.link(pid)
65+
66+
assert_receive :ready
67+
68+
assert {:ok, "\"hi\""} = Runtime.call(pid, {Kernel, :inspect, ["hi"]})
69+
end
4870

49-
pid =
50-
start_supervised!(
51-
{Runtime,
52-
name: "my_proj",
53-
on_initialized: on_init,
54-
task_supervisor: tvisor,
55-
working_dir: cwd,
56-
uri: "file://#{cwd}",
57-
parent: self(),
58-
logger: logger,
59-
db: :some_db,
60-
registry: RuntimeTest.Registry}
61-
)
71+
test "responds with an error when the runtime isn't ready",
72+
%{logger: logger, cwd: cwd, on_init: on_init} do
73+
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
74+
75+
tvisor = start_supervised!(Task.Supervisor)
76+
77+
pid =
78+
start_supervised!(
79+
{Runtime,
80+
task_supervisor: tvisor,
81+
name: "my_proj",
82+
on_initialized: on_init,
83+
working_dir: cwd,
84+
uri: "file://#{cwd}",
85+
parent: self(),
86+
logger: logger,
87+
db: :some_db,
88+
registry: RuntimeTest.Registry}
89+
)
90+
91+
Process.link(pid)
92+
93+
assert {:error, :not_ready} = Runtime.call(pid, {IO, :puts, ["hi"]})
94+
end
95+
end
6296

63-
Process.link(pid)
97+
describe "compile/1" do
98+
test "compiles the project and returns diagnostics",
99+
%{logger: logger, cwd: cwd, on_init: on_init} do
100+
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
101+
102+
tvisor = start_supervised!(Task.Supervisor)
103+
104+
pid =
105+
start_link_supervised!(
106+
{Runtime,
107+
name: "my_proj",
108+
on_initialized: on_init,
109+
task_supervisor: tvisor,
110+
working_dir: cwd,
111+
uri: "file://#{cwd}",
112+
parent: self(),
113+
logger: logger,
114+
db: :some_db,
115+
registry: RuntimeTest.Registry}
116+
)
117+
118+
assert_receive :ready
119+
120+
file = Path.join(cwd, "lib/bar.ex")
121+
122+
assert [
123+
%Mix.Task.Compiler.Diagnostic{
124+
file: ^file,
125+
severity: :warning,
126+
message:
127+
"variable \"arg1\" is unused (if the variable is not meant to be used, prefix it with an underscore)",
128+
position: position,
129+
compiler_name: "Elixir",
130+
details: nil
131+
}
132+
] = Runtime.compile(pid)
133+
134+
if Version.match?(System.version(), ">= 1.15.0") do
135+
assert position == {4, 11}
136+
else
137+
assert position == 4
138+
end
64139

65-
assert_receive :ready
140+
File.write!(file, """
141+
defmodule Bar do
142+
def foo(arg1) do
143+
arg1
144+
end
145+
end
146+
""")
66147

67-
assert {:ok, "\"hi\""} = Runtime.call(pid, {Kernel, :inspect, ["hi"]})
68-
end
148+
assert [] == Runtime.compile(pid)
149+
end
150+
151+
test "emits errors when runtime compilation fails",
152+
%{tmp_dir: tmp_dir, logger: logger, cwd: cwd, on_init: on_init} do
153+
# obvious syntax error
154+
bad_mix_exs = String.replace(mix_exs(), "defmodule", "")
155+
File.write!(Path.join(tmp_dir, "mix.exs"), bad_mix_exs)
69156

70-
test "call returns an error when the runtime is not ready", %{logger: logger, cwd: cwd, on_init: on_init} do
71-
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
157+
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
72158

73-
tvisor = start_supervised!(Task.Supervisor)
159+
tvisor = start_supervised!(Task.Supervisor)
74160

75-
pid =
76161
start_supervised!(
77162
{Runtime,
78163
task_supervisor: tvisor,
@@ -83,95 +168,42 @@ defmodule NextLs.RuntimeTest do
83168
parent: self(),
84169
logger: logger,
85170
db: :some_db,
86-
registry: RuntimeTest.Registry}
171+
registry: RuntimeTest.Registry},
172+
restart: :temporary
87173
)
88174

89-
Process.link(pid)
90-
91-
assert {:error, :not_ready} = Runtime.call(pid, {IO, :puts, ["hi"]})
92-
end
93-
94-
test "compiles the code and returns diagnostics", %{logger: logger, cwd: cwd, on_init: on_init} do
95-
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
175+
assert_receive {:error, {:port_down, :normal}}
96176

97-
tvisor = start_supervised!(Task.Supervisor)
177+
assert_receive {:log, :log, log_msg}
178+
assert log_msg =~ "syntax error"
98179

99-
pid =
100-
start_link_supervised!(
101-
{Runtime,
102-
name: "my_proj",
103-
on_initialized: on_init,
104-
task_supervisor: tvisor,
105-
working_dir: cwd,
106-
uri: "file://#{cwd}",
107-
parent: self(),
108-
logger: logger,
109-
db: :some_db,
110-
registry: RuntimeTest.Registry}
111-
)
112-
113-
assert_receive :ready
114-
115-
file = Path.join(cwd, "lib/bar.ex")
116-
117-
assert [
118-
%Mix.Task.Compiler.Diagnostic{
119-
file: ^file,
120-
severity: :warning,
121-
message:
122-
"variable \"arg1\" is unused (if the variable is not meant to be used, prefix it with an underscore)",
123-
position: position,
124-
compiler_name: "Elixir",
125-
details: nil
126-
}
127-
] = Runtime.compile(pid)
128-
129-
if Version.match?(System.version(), ">= 1.15.0") do
130-
assert position == {4, 11}
131-
else
132-
assert position == 4
180+
assert_receive {:log, :error, error_msg}
181+
assert error_msg =~ "{:shutdown, {:port_down, :normal}}"
133182
end
134183

135-
File.write!(file, """
136-
defmodule Bar do
137-
def foo(arg1) do
138-
arg1
139-
end
184+
test "responds with an error when the runtime isn't ready",
185+
%{logger: logger, cwd: cwd, on_init: on_init} do
186+
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
187+
188+
tvisor = start_supervised!(Task.Supervisor)
189+
190+
pid =
191+
start_supervised!(
192+
{Runtime,
193+
task_supervisor: tvisor,
194+
name: "my_proj",
195+
on_initialized: on_init,
196+
working_dir: cwd,
197+
uri: "file://#{cwd}",
198+
parent: self(),
199+
logger: logger,
200+
db: :some_db,
201+
registry: RuntimeTest.Registry}
202+
)
203+
204+
Process.link(pid)
205+
206+
assert {:error, :not_ready} = Runtime.compile(pid)
140207
end
141-
""")
142-
143-
assert [] == Runtime.compile(pid)
144-
end
145-
146-
test "emits errors when runtime compilation fails", %{tmp_dir: tmp_dir, logger: logger, cwd: cwd, on_init: on_init} do
147-
# obvious syntax error
148-
bad_mix_exs = String.replace(mix_exs(), "defmodule", "")
149-
File.write!(Path.join(tmp_dir, "mix.exs"), bad_mix_exs)
150-
151-
start_supervised!({Registry, keys: :duplicate, name: RuntimeTest.Registry})
152-
153-
tvisor = start_supervised!(Task.Supervisor)
154-
155-
start_supervised!(
156-
{Runtime,
157-
task_supervisor: tvisor,
158-
name: "my_proj",
159-
on_initialized: on_init,
160-
working_dir: cwd,
161-
uri: "file://#{cwd}",
162-
parent: self(),
163-
logger: logger,
164-
db: :some_db,
165-
registry: RuntimeTest.Registry},
166-
restart: :temporary
167-
)
168-
169-
assert_receive {:error, {:port_down, :normal}}
170-
171-
assert_receive {:log, :log, log_msg}
172-
assert log_msg =~ "syntax error"
173-
174-
assert_receive {:log, :error, error_msg}
175-
assert error_msg =~ "{:shutdown, {:port_down, :normal}}"
176208
end
177209
end

0 commit comments

Comments
 (0)