Skip to content

Commit 513ed0b

Browse files
author
José Valim
committed
Move on handler functionality to its own module
1 parent 3bbae0a commit 513ed0b

File tree

7 files changed

+138
-111
lines changed

7 files changed

+138
-111
lines changed

lib/ex_unit/lib/ex_unit.ex

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,15 @@ defmodule ExUnit do
101101

102102
@doc false
103103
def start(_type, []) do
104-
pid = ExUnit.Sup.start_link
105-
ExUnit.Server.start_load
106-
pid
104+
import Supervisor.Spec
105+
106+
children = [
107+
worker(ExUnit.Server, []),
108+
worker(ExUnit.OnExitHandler, [])
109+
]
110+
111+
opts = [strategy: :one_for_one, name: ExUnit.Supervisor]
112+
Supervisor.start_link(children, opts)
107113
end
108114

109115
@doc """

lib/ex_unit/lib/ex_unit/callbacks.ex

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,10 @@ defmodule ExUnit.Callbacks do
145145

146146
@spec on_exit(term, (() -> term)) :: :ok
147147
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
148+
case ExUnit.OnExitHandler.add(self, ref, callback) do
149+
:ok -> :ok
150+
:error ->
151+
raise ArgumentError, "on_exit/1 callback can only be invoked from the test process"
151152
end
152153
end
153154

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
defmodule ExUnit.OnExitHandler do
2+
@moduledoc false
3+
@name __MODULE__
4+
5+
def start_link do
6+
Agent.start_link(fn -> %{} end, name: @name)
7+
end
8+
9+
@spec register(pid) :: :ok
10+
def register(pid) do
11+
Agent.update(@name, &Map.put(&1, pid, []))
12+
end
13+
14+
@spec add(pid, term, fun) :: :ok | :error
15+
def add(pid, ref, callback) do
16+
Agent.get_and_update(@name, fn map ->
17+
if entries = Map.get(map, pid) do
18+
entries = List.keystore(entries, ref, 0, {ref, callback})
19+
{:ok, Map.put(map, pid, entries)}
20+
else
21+
{:error, map}
22+
end
23+
end)
24+
end
25+
26+
@spec run(pid) :: :ok | {Exception.kind, term, Exception.stacktrace}
27+
def run(pid) do
28+
callbacks = Agent.get_and_update(@name, &Map.pop(&1, pid))
29+
exec_on_exit_callbacks(Enum.reverse(callbacks))
30+
end
31+
32+
defp exec_on_exit_callbacks(callbacks) do
33+
{runner_pid, runner_monitor, state} =
34+
Enum.reduce callbacks, {nil, nil, nil}, &exec_on_exit_callback/2
35+
36+
if is_pid(runner_pid) and Process.alive?(runner_pid) do
37+
send(runner_pid, :shutdown)
38+
receive do
39+
{:DOWN, ^runner_monitor, :process, ^runner_pid, _error} -> :ok
40+
end
41+
end
42+
43+
state || :ok
44+
end
45+
46+
defp exec_on_exit_callback({_ref, callback}, {runner_pid, runner_monitor, state}) do
47+
{runner_pid, runner_monitor} = ensure_alive_callback_runner(runner_pid, runner_monitor)
48+
send(runner_pid, {:run, self(), callback})
49+
50+
receive do
51+
{^runner_pid, nil} ->
52+
{runner_pid, runner_monitor, state}
53+
{^runner_pid, error} ->
54+
{runner_pid, runner_monitor, state || error}
55+
{:DOWN, ^runner_monitor, :process, ^runner_pid, error} ->
56+
{nil, nil, state || {{:EXIT, runner_pid}, error, []}}
57+
end
58+
end
59+
60+
## Runner
61+
62+
@doc false
63+
def on_exit_runner_loop do
64+
receive do
65+
{:run, from, fun} ->
66+
send(from, {self(), exec_callback(fun)})
67+
on_exit_runner_loop()
68+
:shutdown ->
69+
:ok
70+
end
71+
end
72+
73+
defp ensure_alive_callback_runner(nil, nil) do
74+
spawn_monitor(__MODULE__, :on_exit_runner_loop, [])
75+
end
76+
77+
defp ensure_alive_callback_runner(runner_pid, runner_monitor) do
78+
{runner_pid, runner_monitor}
79+
end
80+
81+
defp exec_callback(callback) do
82+
callback.()
83+
nil
84+
catch
85+
kind, error ->
86+
{kind, Exception.normalize(kind, error), System.stacktrace}
87+
end
88+
end

lib/ex_unit/lib/ex_unit/runner.ex

Lines changed: 26 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ defmodule ExUnit.Runner do
143143
{case_pid, case_ref} =
144144
spawn_monitor(fn ->
145145
case exec_case_setup(test_case) do
146-
{:ok, {test_case, context}} ->
146+
{:ok, test_case, context} ->
147147
Enum.each(tests, &run_test(config, &1, context))
148148
test_case = exec_case_teardown(test_case, context)
149149
send self_pid, {self, :case_finished, test_case, []}
@@ -164,7 +164,7 @@ defmodule ExUnit.Runner do
164164

165165
defp exec_case_setup(%ExUnit.TestCase{name: case_name} = test_case) do
166166
{:ok, context} = case_name.__ex_unit__(:setup_all, %{case: case_name})
167-
{:ok, {test_case, context}}
167+
{:ok, test_case, context}
168168
catch
169169
kind, error ->
170170
failed = {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace}}
@@ -195,14 +195,13 @@ defmodule ExUnit.Runner do
195195

196196
{test_pid, test_ref} =
197197
spawn_monitor(fn ->
198-
Process.put(ExUnit.Runner.Pid, self_pid)
198+
ExUnit.OnExitHandler.register(self)
199+
199200
{us, test} =
200201
:timer.tc(fn ->
201202
case exec_test_setup(test, context) do
202-
{:ok, {test, context}} ->
203-
if nil?(test.state) do
204-
test = exec_test(test, context)
205-
end
203+
{:ok, test, context} ->
204+
test = exec_test(test, context)
206205
exec_test_teardown(test, context)
207206
{:error, test} ->
208207
test
@@ -212,34 +211,23 @@ defmodule ExUnit.Runner do
212211
send self_pid, {self, :test_finished, %{test | time: us}}
213212
end)
214213

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
219-
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)
224-
{^test_pid, :test_finished, 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
214+
test =
215+
receive do
216+
{^test_pid, :test_finished, test} ->
229217
test
230-
end
231-
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
232-
exec_on_exit_callbacks(on_exit_callbacks)
233-
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
234-
end
218+
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
219+
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
220+
end
221+
222+
exec_on_exit(test, test_pid)
235223
end
236224

237225
defp exec_test_setup(%ExUnit.Test{case: case} = test, context) do
238226
{:ok, context} = case.__ex_unit__(:setup, context)
239-
{:ok, {test, context}}
227+
{:ok, test, context}
240228
catch
241229
kind2, error2 ->
242-
failed = {:failed, {kind2, Exception.normalize(kind2, error2), pruned_stacktrace}}
230+
failed = {:failed, {kind2, Exception.normalize(kind2, error2), pruned_stacktrace()}}
243231
{:error, %{test | state: failed}}
244232
end
245233

@@ -248,7 +236,7 @@ defmodule ExUnit.Runner do
248236
test
249237
catch
250238
kind, error ->
251-
failed = {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace}}
239+
failed = {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace()}}
252240
%{test | state: failed}
253241
end
254242

@@ -257,66 +245,20 @@ defmodule ExUnit.Runner do
257245
test
258246
catch
259247
kind, error ->
260-
if nil?(test.state) do
261-
%{test | state: {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace}}}
262-
else
263-
test
264-
end
248+
state = test.state || {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace()}}
249+
%{test | state: state}
265250
end
266251

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
252+
defp exec_on_exit(test, test_pid) do
253+
case ExUnit.OnExitHandler.run(test_pid) do
254+
:ok ->
255+
test
256+
{kind, reason, stack} ->
257+
state = test.state || {:failed, {kind, reason, prune_stacktrace(stack)}}
258+
%{test | state: state}
308259
end
309260
end
310261

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-
320262
## Helpers
321263

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

lib/ex_unit/lib/ex_unit/server.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ defmodule ExUnit.Server do
4242

4343
def init(:ok) do
4444
config = %{async_cases: HashSet.new, sync_cases: HashSet.new,
45-
start_load: nil, captured_devices: HashSet.new}
45+
start_load: :os.timestamp, captured_devices: HashSet.new}
4646
{:ok, config}
4747
end
4848

lib/ex_unit/lib/ex_unit/sup.ex

Lines changed: 0 additions & 14 deletions
This file was deleted.

lib/ex_unit/test/ex_unit/callbacks_test.exs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,16 @@ defmodule ExUnit.CallbacksTest do
206206
end
207207

208208
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"
209+
output = capture_io(fn -> ExUnit.run end)
210+
211+
assert output =~ """
212+
on_exit 2 overrides -> run
213+
simple on_exit run
214+
on_exit 1 overrides -> run
215+
on_exit setup run
216+
"""
217+
218+
refute output =~ "not run"
215219
end
216220
end
217221

0 commit comments

Comments
 (0)