Skip to content

Integrate start_supervised supervisor with :$caller #13253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/ex_unit/lib/ex_unit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,7 @@ defmodule ExUnit do
def fetch_test_supervisor() do
case ExUnit.OnExitHandler.get_supervisor(self()) do
{:ok, nil} ->
opts = [strategy: :one_for_one, max_restarts: 1_000_000, max_seconds: 1]
{:ok, sup} = Supervisor.start_link([], opts)
{:ok, sup} = ExUnit.OnExitHandler.Supervisor.start_link([])
ExUnit.OnExitHandler.put_supervisor(self(), sup)
{:ok, sup}

Expand Down
22 changes: 16 additions & 6 deletions lib/ex_unit/lib/ex_unit/callbacks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,13 @@ defmodule ExUnit.Callbacks do
See the `Supervisor` module for a discussion on child specifications
and the available specification keys.

The started process is not linked to the test process and a crash will
not necessarily fail the test. To start and link a process to guarantee
that any crash would also fail the test use `start_link_supervised!/2`.

This function returns `{:ok, pid}` in case of success, otherwise it
returns `{:error, reason}`.

The advantage of starting a process under the test supervisor is that
it is guaranteed to exit before the next test starts. Therefore, you
don't need to remove the process at the end of your tests via
Expand All @@ -528,12 +535,15 @@ defmodule ExUnit.Callbacks do
test, as simply shutting down the process would cause it to be restarted
according to its `:restart` value.

The started process is not linked to the test process and a crash will
not necessarily fail the test. To start and link a process to guarantee
that any crash would also fail the test use `start_link_supervised!/2`.

This function returns `{:ok, pid}` in case of success, otherwise it
returns `{:error, reason}`.
Another advantage is that the test process will act as both an ancestor
as well as a caller to the supervised processes. When a process is started
under a supervision tree, it typically populates the `$ancestors` key in
its process dictionary with all of its ancestors, which will include the test
process. Additionally, `start_supervised/2` will also store the test process
in the `$callers` key of the started process, allowing tools that perform
either ancestor or caller tracking to reach the test process. You can learn
more about these keys in
[the `Task` module](`Task#module-ancestor-and-caller-tracking`).
"""
@doc since: "1.5.0"
@spec start_supervised(Supervisor.child_spec() | module | {module, term}, keyword) ::
Expand Down
25 changes: 25 additions & 0 deletions lib/ex_unit/lib/ex_unit/on_exit_handler/supervisor.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule ExUnit.OnExitHandler.Supervisor do
@moduledoc false
use Supervisor

def start_link(children) do
Supervisor.start_link(__MODULE__, {children, get_callers(self())})
end

@impl true
def init({children, callers}) do
put_callers(callers)
Supervisor.init(children, strategy: :one_for_one, max_restarts: 1_000_000, max_seconds: 1)
end

defp get_callers(owner) do
case :erlang.get(:"$callers") do
[_ | _] = list -> [owner | list]
_ -> [owner]
end
end

defp put_callers(callers) do
:erlang.put(:"$callers", callers)
end
end
18 changes: 18 additions & 0 deletions lib/ex_unit/test/ex_unit/supervised_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ defmodule ExUnit.SupervisedTest do
end
end

test "starts a supervised process with correct :\"$callers\"" do
test_pid = self()
fun = fn -> send(test_pid, {:callers, Process.get(:"$callers")}) end
{:ok, _pid} = start_supervised({Task, fun})

assert_receive {:callers, callers}
assert List.last(callers) == test_pid
end

test "starts a supervised process with correct :\"$ancestors\"" do
test_pid = self()
fun = fn -> send(test_pid, {:ancestors, Process.get(:"$ancestors")}) end
{:ok, _pid} = start_supervised({Task, fun})

assert_receive {:ancestors, ancestors}
assert List.last(ancestors) == test_pid
end

test "stops a supervised process" do
{:ok, pid} = start_supervised({MyAgent, 0})
assert stop_supervised(MyAgent) == :ok
Expand Down