Skip to content

Commit 3bbae0a

Browse files
author
José Valim
committed
Merge pull request #2365 from MSch/exunit-on-exit
Support on_exit for registering callbacks in ExUnit.
2 parents 2134db3 + 40dff7e commit 3bbae0a

File tree

3 files changed

+160
-1
lines changed

3 files changed

+160
-1
lines changed

lib/ex_unit/lib/ex_unit/callbacks.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ defmodule ExUnit.Callbacks do
143143
end
144144
end
145145

146+
@spec on_exit(term, (() -> term)) :: :ok
147+
def on_exit(ref \\ make_ref, callback) do
148+
send(Process.get(ExUnit.Runner.Pid), {self(), :register_on_exit_callback, ref, callback})
149+
receive do
150+
{:registered_on_exit_callback, ^ref} -> :ok
151+
end
152+
end
153+
146154
## Helpers
147155

148156
@doc false

lib/ex_unit/lib/ex_unit/runner.ex

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ defmodule ExUnit.Runner do
195195

196196
{test_pid, test_ref} =
197197
spawn_monitor(fn ->
198+
Process.put(ExUnit.Runner.Pid, self_pid)
198199
{us, test} =
199200
:timer.tc(fn ->
200201
case exec_test_setup(test, context) do
@@ -211,10 +212,24 @@ defmodule ExUnit.Runner do
211212
send self_pid, {self, :test_finished, %{test | time: us}}
212213
end)
213214

215+
loop_test(test, test_pid, test_ref, HashDict.new)
216+
end
217+
218+
defp loop_test(test, test_pid, test_ref, on_exit_callbacks) do
214219
receive do
220+
{^test_pid, :register_on_exit_callback, ref, callback} ->
221+
on_exit_callbacks = Dict.put(on_exit_callbacks, ref, callback)
222+
send(test_pid, {:registered_on_exit_callback, ref})
223+
loop_test(test, test_pid, test_ref, on_exit_callbacks)
215224
{^test_pid, :test_finished, test} ->
216-
test
225+
on_exit_state = exec_on_exit_callbacks(on_exit_callbacks)
226+
if nil?(test.state) do
227+
%{test | state: on_exit_state}
228+
else
229+
test
230+
end
217231
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
232+
exec_on_exit_callbacks(on_exit_callbacks)
218233
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
219234
end
220235
end
@@ -249,6 +264,59 @@ defmodule ExUnit.Runner do
249264
end
250265
end
251266

267+
defp exec_on_exit_callbacks(callbacks) do
268+
{runner_pid, runner_monitor, callback_state} = Enum.reduce Dict.values(callbacks), {nil, nil, nil}, fn(callback, {runner_pid, runner_monitor, callback_state}) ->
269+
{runner_pid, runner_monitor} = ensure_alive_callback_runner(runner_pid, runner_monitor)
270+
send(runner_pid, {:run, self(), callback})
271+
receive do
272+
{^runner_pid, nil} -> {runner_pid, runner_monitor, callback_state}
273+
{^runner_pid, error} ->
274+
if nil?(callback_state) do
275+
{runner_pid, runner_monitor, error}
276+
else
277+
{runner_pid, runner_monitor, callback_state}
278+
end
279+
{:DOWN, ^runner_monitor, :process, ^runner_pid, error} ->
280+
if nil?(callback_state) do
281+
{nil, nil, {:failed, {{:EXIT, runner_pid}, error, []}}}
282+
else
283+
{nil, nil, callback_state}
284+
end
285+
end
286+
end
287+
288+
if is_pid(runner_pid) and Process.alive?(runner_pid) do
289+
send(runner_pid, :shutdown)
290+
receive do
291+
{:DOWN, ^runner_monitor, :process, ^runner_pid, _error} -> :ok
292+
end
293+
end
294+
295+
callback_state
296+
end
297+
298+
defp ensure_alive_callback_runner(nil, nil), do: spawn_monitor(__MODULE__, :on_exit_runner_loop, [])
299+
defp ensure_alive_callback_runner(runner_pid, runner_monitor), do: {runner_pid, runner_monitor}
300+
301+
def on_exit_runner_loop do
302+
receive do
303+
{:run, from, fun} ->
304+
result = exec_on_exit_callback(fun)
305+
send(from, {self(), result})
306+
on_exit_runner_loop()
307+
:shutdown -> :ok
308+
end
309+
end
310+
311+
defp exec_on_exit_callback(callback) do
312+
callback.()
313+
nil
314+
catch
315+
kind, error ->
316+
{:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace}}
317+
end
318+
319+
252320
## Helpers
253321

254322
defp shuffle(%{seed: 0}, list) do

lib/ex_unit/test/ex_unit/callbacks_test.exs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,89 @@ defmodule ExUnit.CallbacksTest do
130130
assert capture_io(fn -> ExUnit.run end) =~
131131
"** (MatchError) no match of right hand side value: :error"
132132
end
133+
134+
test "doesn't choke on on_exit errors" do
135+
defmodule OnExitErrorTest do
136+
use ExUnit.Case, async: false
137+
138+
test "ok" do
139+
on_exit fn ->
140+
:ok = error
141+
end
142+
143+
:ok
144+
end
145+
146+
defp error, do: :error
147+
end
148+
149+
ExUnit.configure(formatters: [ExUnit.CLIFormatter])
150+
151+
assert capture_io(fn -> ExUnit.run end) =~
152+
"** (MatchError) no match of right hand side value: :error"
153+
end
154+
155+
test "doesn't choke when on_exit exits" do
156+
defmodule OnExitExitTest do
157+
use ExUnit.Case, async: false
158+
159+
test "ok" do
160+
on_exit fn ->
161+
Process.exit(self(), :kill)
162+
end
163+
164+
:ok
165+
end
166+
end
167+
168+
ExUnit.configure(formatters: [ExUnit.CLIFormatter])
169+
assert capture_io(fn -> ExUnit.run end) =~
170+
">) killed"
171+
end
172+
173+
test "runs multiple on_exit exits and overrides by ref" do
174+
defmodule OnExitOverrideTest do
175+
use ExUnit.Case, async: false
176+
177+
setup do
178+
on_exit fn ->
179+
IO.puts "on_exit setup run"
180+
end
181+
182+
on_exit {:overridden, 1}, fn ->
183+
IO.puts "on_exit 1 overridden -> not run"
184+
end
185+
end
186+
187+
test "ok" do
188+
on_exit fn ->
189+
IO.puts "simple on_exit run"
190+
end
191+
192+
on_exit {:overridden, 2}, fn ->
193+
IO.puts "on_exit 2 overridden -> not run"
194+
end
195+
196+
on_exit {:overridden, 2}, fn ->
197+
IO.puts "on_exit 2 overrides -> run"
198+
end
199+
200+
on_exit {:overridden, 1}, fn ->
201+
IO.puts "on_exit 1 overrides -> run"
202+
end
203+
204+
:ok
205+
end
206+
end
207+
208+
ExUnit.configure(formatters: [ExUnit.CLIFormatter])
209+
captured_id = capture_io(fn -> ExUnit.run end)
210+
assert captured_id =~ "on_exit setup run"
211+
assert captured_id =~ "simple on_exit run"
212+
refute captured_id =~ "not run"
213+
assert captured_id =~ "on_exit 1 overrides -> run"
214+
assert captured_id =~ "on_exit 2 overrides -> run"
215+
end
133216
end
134217

135218
defmodule ExUnit.CallbacksNoTests do

0 commit comments

Comments
 (0)