@@ -3,29 +3,43 @@ defmodule ExUnit.Callbacks do
3
3
Defines ExUnit callbacks.
4
4
5
5
This module defines both `setup_all` and `setup` callbacks, as well as
6
- the `on_exit/2` function .
6
+ the `on_exit/2`, `start_supervised/2` and `stop_supervised/1` functions .
7
7
8
8
The setup callbacks are defined via macros and each one can optionally
9
9
receive a map with metadata, usually referred to as `context`. The
10
10
callback may optionally put extra data into the `context` to be used in
11
11
the tests.
12
12
13
- The `setup_all` callbacks are invoked only once to setup the test case before any
14
- test is run and all `setup` callbacks are run before each test. No callback
13
+ The `setup_all` callbacks are invoked only once per module, before any
14
+ test runs. All `setup` callbacks are run before each test. No callback
15
15
runs if the test case has no tests or all tests have been filtered out.
16
16
17
+ `start_supervised/2` is used to start processes under a supervisor. The
18
+ supervisor is linked to the current test process. The supervisor as well
19
+ as all child processes are guaranteed to terminate before any `on_exit/2`
20
+ callback runs.
21
+
17
22
`on_exit/2` callbacks are registered on demand, usually to undo an action
18
23
performed by a setup callback. `on_exit/2` may also take a reference,
19
24
allowing callback to be overridden in the future. A registered `on_exit/2`
20
25
callback always runs, while failures in `setup` and `setup_all` will stop
21
26
all remaining setup callbacks from executing.
22
27
23
- Finally, `setup_all` callbacks run in the test case process, while all
24
- `setup` callbacks run in the same process as the test itself. `on_exit/2`
25
- callbacks always run in a separate process than the test case or the
26
- test itself. Since the test process exits with reason `:shutdown`, most
27
- of times `on_exit/2` can be avoided as processes are going to clean
28
- up on their own.
28
+ Finally, `setup_all` callbacks run in a separate process per module, while
29
+ all `setup` callbacks run in the same process as the test itself. `on_exit/2`
30
+ callbacks always run in a separate process, as implied by their name. The
31
+ test process always exits with reason `:shutdown`, which means any process
32
+ linked to the test process will also exit, although asynchronously. Therefore
33
+ it is preferred to use `start_supervised/2` to guarantee synchronous termination.
34
+
35
+ Here is a run down of the life-cycle of the test process:
36
+
37
+ 1. the test process is spawned
38
+ 2. it runs `setup/2` callbacks
39
+ 3. it runs the test itself
40
+ 4. it stops all supervised processes
41
+ 5. the test process exits with reason `:shutdown`
42
+ 6. `on_exit/2` callbacks are executed in a separate process
29
43
30
44
## Context
31
45
@@ -49,41 +63,40 @@ defmodule ExUnit.Callbacks do
49
63
defmodule AssertionTest do
50
64
use ExUnit.Case, async: true
51
65
52
- # "setup_all" is called once to setup the case before any test is run
66
+ # "setup_all" is called once per module before any test runs
53
67
setup_all do
54
68
IO.puts "Starting AssertionTest"
55
69
56
70
# No context is returned here
57
71
:ok
58
72
end
59
73
60
- # "setup" is called before each test is run
74
+ # "setup" is called before each test
61
75
setup do
62
- IO.puts "This is a setup callback"
76
+ IO.puts "This is a setup callback for #{inspect self()} "
63
77
64
78
on_exit fn ->
65
- IO.puts "This is invoked once the test is done"
79
+ IO.puts "This is invoked once the test is done. Process: #{inspect self()} "
66
80
end
67
81
68
82
# Returns extra metadata to be merged into context
69
83
[hello: "world"]
70
84
end
71
85
72
- # Same as "setup", but receives the context
73
- # for the current test
86
+ # Same as above, but receives the context as argument
74
87
setup context do
75
- IO.puts "Setting up: #{context[: test] }"
88
+ IO.puts "Setting up: #{context. test}"
76
89
:ok
77
90
end
78
91
79
- # Setups can also invoke a local or imported function that can return a context
92
+ # Setups can also invoke a local or imported function that returns a context
80
93
setup :invoke_local_or_imported_function
81
94
82
95
test "always pass" do
83
96
assert true
84
97
end
85
98
86
- test "another one ", context do
99
+ test "uses metadata from setup ", context do
87
100
assert context[:hello] == "world"
88
101
end
89
102
@@ -198,15 +211,16 @@ defmodule ExUnit.Callbacks do
198
211
end
199
212
200
213
@ doc """
201
- Defines a callback that runs on the test (or test case) exit .
214
+ Defines a callback that runs once the test exits .
202
215
203
216
`callback` is a function that receives no arguments and
204
217
runs in a separate process than the caller.
205
218
206
- `on_exit/2` is usually called from `setup` and `setup_all` callbacks,
207
- often to undo the action performed during `setup`. However, `on_exit/2`
208
- may also be called dynamically, where a reference can be used to
209
- guarantee the callback will be invoked only once.
219
+ `on_exit/2` is usually called from `setup` and `setup_all`
220
+ callbacks, often to undo the action performed during `setup`.
221
+ However, `on_exit/2` may also be called dynamically, where a
222
+ reference can be used to guarantee the callback will be invoked
223
+ only once.
210
224
"""
211
225
@ spec on_exit ( term , ( ( ) -> term ) ) :: :ok | no_return
212
226
def on_exit ( name_or_ref \\ make_ref ( ) , callback ) when is_function ( callback , 0 ) do
@@ -217,6 +231,85 @@ defmodule ExUnit.Callbacks do
217
231
end
218
232
end
219
233
234
+ @ supervisor_opts [ strategy: :one_for_one , max_restarts: 1_000_000 , max_seconds: 1 ]
235
+
236
+ @ doc """
237
+ Starts a child process under the test supervisor.
238
+
239
+ It expects a child specification or a module, similar to the ones
240
+ given to `Supervisor.start_link/2`. For example, if your application
241
+ starts a supervision tree by running:
242
+
243
+ Supervisor.start_link([MyServer, {OtherSupervisor, ...}], ...)
244
+
245
+ You can start those processes under test in isolation by running:
246
+
247
+ start_supervised(MyServer)
248
+ start_supervised({OtherSupervisor, :initial_value})
249
+
250
+ A keyword list can also be given if there is a need to change
251
+ the child specification for the given child process:
252
+
253
+ start_supervised({MyServer, :initial_value}, restart: :temporary)
254
+
255
+ See the `Supervisor` module for a discussion on child specifications
256
+ and the available specification keys.
257
+
258
+ The advantage of starting a process under the test supervisor is that
259
+ it is guaranteed to exit before the next test starts. Furthermore,
260
+ because the child process is supervised, it will be restarted in case
261
+ of crashes according to the `:restart` strategy in the child
262
+ specification, even if stopped manually. Therefore, to guarantee a
263
+ process started with `start_supervised/2` terminates without restarts,
264
+ see `stop_supervised/1`.
265
+
266
+ This function returns `{:ok, pid}` in case of success, otherwise it
267
+ returns `{:error, reason}`.
268
+ """
269
+ @ spec start_supervised ( Supervisor . child_spec | module | { module , term } , keyword ) ::
270
+ Supervisor . on_start_child
271
+ def start_supervised ( child_spec_or_module , opts \\ [ ] ) do
272
+ sup =
273
+ case ExUnit.OnExitHandler . get_supervisor ( self ( ) ) do
274
+ { :ok , nil } ->
275
+ { :ok , sup } = Supervisor . start_link ( [ ] , @ supervisor_opts )
276
+ ExUnit.OnExitHandler . put_supervisor ( self ( ) , sup )
277
+ sup
278
+ { :ok , sup } ->
279
+ sup
280
+ :error ->
281
+ raise ArgumentError , "start_supervised/2 can only be invoked from the test process"
282
+ end
283
+
284
+ Supervisor . start_child ( sup , Supervisor . child_spec ( child_spec_or_module , opts ) )
285
+ end
286
+
287
+ @ doc """
288
+ Stops a child process started via `start_supervised/2`.
289
+
290
+ This function expects the `id` in the child specification.
291
+ For example:
292
+
293
+ {:ok, _} = start_supervised(MyServer)
294
+ :ok = stop_supervised(MyServer)
295
+
296
+ It returns `:ok` if there is a supervised process with such
297
+ `id`, `{:error, :not_found}` otherwise.
298
+ """
299
+ @ spec stop_supervised ( id :: term ( ) ) :: :ok | { :error , :not_found }
300
+ def stop_supervised ( id ) do
301
+ case ExUnit.OnExitHandler . get_supervisor ( self ( ) ) do
302
+ { :ok , nil } ->
303
+ { :error , :not_found }
304
+ { :ok , sup } ->
305
+ with :ok <- Supervisor . terminate_child ( sup , id ) ,
306
+ :ok <- Supervisor . delete_child ( sup , id ) ,
307
+ do: :ok
308
+ :error ->
309
+ raise ArgumentError , "stop_supervised/1 can only be invoked from the test process"
310
+ end
311
+ end
312
+
220
313
## Helpers
221
314
222
315
@ reserved [ :case , :file , :line , :test , :async , :registered , :describe ]
@@ -238,7 +331,6 @@ defmodule ExUnit.Callbacks do
238
331
merge ( mod , context , value , value )
239
332
end
240
333
241
- @ doc false
242
334
defp merge ( _mod , context , :ok , _original_value ) do
243
335
context
244
336
end
0 commit comments