Skip to content

Commit 6e9f9f2

Browse files
author
José Valim
committed
Kill test process only after on_exit runs
We do this to avoid links created during the test from crashing when the test exits which happened before the on_exit callbacks run. This was particularly troubling with GenServer, GenEvent and Supervisor, in which the first does not crash when a linked process exits with normal reason, but the other two do. Due to this behaviour, before this change there was a possibility of race conditions causing tests to crash on on_exit. In case the test process exits with a non-normal reason, all links will be broken, so the problem doesn't manifest in such cases.
1 parent db25e24 commit 6e9f9f2

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

lib/ex_unit/lib/ex_unit/runner.ex

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ defmodule ExUnit.Runner do
138138
end
139139

140140
defp spawn_case(config, test_case, tests) do
141-
self_pid = self
141+
parent = self
142142

143143
{case_pid, case_ref} =
144144
spawn_monitor(fn ->
@@ -148,12 +148,14 @@ defmodule ExUnit.Runner do
148148
{:ok, test_case, context} ->
149149
Enum.each(tests, &run_test(config, &1, context))
150150
test_case = exec_case_teardown(test_case, context)
151-
send self_pid, {self, :case_finished, test_case, []}
151+
send parent, {self, :case_finished, test_case, []}
152152

153153
{:error, test_case} ->
154154
failed_tests = Enum.map(tests, & %{&1 | state: {:invalid, test_case}})
155-
send self_pid, {self, :case_finished, test_case, failed_tests}
155+
send parent, {self, :case_finished, test_case, failed_tests}
156156
end
157+
158+
receive do: ({^parent, :done} -> :done)
157159
end)
158160

159161
{test_case, pending} =
@@ -165,7 +167,9 @@ defmodule ExUnit.Runner do
165167
{test_case, []}
166168
end
167169

168-
{exec_on_exit(test_case, case_pid), pending}
170+
test_case = exec_on_exit(test_case, case_pid)
171+
send case_pid, {parent, :done}
172+
{test_case, pending}
169173
end
170174

171175
defp exec_case_setup(%ExUnit.TestCase{name: case_name} = test_case) do
@@ -197,7 +201,7 @@ defmodule ExUnit.Runner do
197201
end
198202

199203
defp spawn_test(_config, test, context) do
200-
self_pid = self
204+
parent = self()
201205

202206
{test_pid, test_ref} =
203207
spawn_monitor(fn ->
@@ -214,7 +218,8 @@ defmodule ExUnit.Runner do
214218
end
215219
end)
216220

217-
send self_pid, {self, :test_finished, %{test | time: us}}
221+
send parent, {self, :test_finished, %{test | time: us}}
222+
receive do: ({^parent, :done} -> :done)
218223
end)
219224

220225
test =
@@ -225,7 +230,9 @@ defmodule ExUnit.Runner do
225230
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
226231
end
227232

228-
exec_on_exit(test, test_pid)
233+
test = exec_on_exit(test, test_pid)
234+
send test_pid, {parent, :done}
235+
test
229236
end
230237

231238
defp exec_test_setup(%ExUnit.Test{case: case} = test, context) do

lib/ex_unit/test/ex_unit/callbacks_test.exs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ defmodule ExUnit.CallbacksTest do
5353

5454
test "doesn't choke on setup_all errors" do
5555
defmodule SetupAllTest do
56-
use ExUnit.Case, async: false
56+
use ExUnit.Case
5757

5858
setup_all _ do
5959
:ok = error
@@ -72,7 +72,7 @@ defmodule ExUnit.CallbacksTest do
7272

7373
test "doesn't choke on on_exit errors" do
7474
defmodule OnExitErrorTest do
75-
use ExUnit.Case, async: false
75+
use ExUnit.Case
7676

7777
test "ok" do
7878
on_exit fn -> :ok = error end
@@ -105,9 +105,37 @@ defmodule ExUnit.CallbacksTest do
105105
on_exit fn -> ExUnit.configure(formatters: [ExUnit.CLIFormatter]) end
106106
end
107107

108+
test "kills test process only after on_exit runs" do
109+
defmodule OnExitAliveTest do
110+
use ExUnit.Case
111+
112+
setup do
113+
pid = spawn_link fn ->
114+
Process.flag(:trap_exit, true)
115+
receive do: ({:EXIT, _, _} -> :ok)
116+
end
117+
118+
on_exit fn ->
119+
assert Process.alive?(pid)
120+
IO.puts "on_exit run"
121+
end
122+
123+
:ok
124+
end
125+
126+
test "ok" do
127+
:ok
128+
end
129+
end
130+
131+
output = capture_io(fn -> ExUnit.run end)
132+
assert output =~ "on_exit run"
133+
assert output =~ "1 tests, 0 failures"
134+
end
135+
108136
test "runs multiple on_exit exits and overrides by ref" do
109137
defmodule OnExitSuccessTest do
110-
use ExUnit.Case, async: false
138+
use ExUnit.Case
111139

112140
setup do
113141
on_exit fn ->
@@ -166,7 +194,7 @@ defmodule ExUnit.CallbacksTest do
166194

167195
test "runs multiple on_exit on failure" do
168196
defmodule OnExitFailureTest do
169-
use ExUnit.Case, async: false
197+
use ExUnit.Case
170198

171199
setup do
172200
on_exit fn ->

0 commit comments

Comments
 (0)