Skip to content
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

Optimize stub_with #157

Merged
merged 2 commits into from
Jun 22, 2024
Merged
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
80 changes: 54 additions & 26 deletions lib/mox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -647,11 +647,17 @@ defmodule Mox do
end

defp do_stub_with(mock, module, behaviours, _behaviours_common) do
for behaviour <- behaviours,
{fun, arity} <- behaviour.behaviour_info(:callbacks),
function_exported?(mock, fun, arity) do
stub(mock, fun, :erlang.make_fun(module, fun, arity))
end
key_expectation_list =
for behaviour <- behaviours,
{fun, arity} <- behaviour.behaviour_info(:callbacks),
function_exported?(mock, fun, arity) do
{
{mock, fun, arity},
{0, [], :erlang.make_fun(module, fun, arity)}
}
end

add_expectations!(mock, key_expectation_list)

mock
end
Expand All @@ -675,30 +681,47 @@ defmodule Mox do
raise ArgumentError, "unknown function #{name}/#{arity} for mock #{inspect(mock)}"
end

case add_expectation(self(), key, value) do
case add_expectations(self(), mock, [{key, value}]) do
:ok ->
:ok

{:error, {:currently_allowed, owner_pid}} ->
inspected = inspect(self())
{:error, error} ->
raise ArgumentError, expectation_error_to_message(error, mock)
end
end

raise ArgumentError, """
cannot add expectations/stubs to #{inspect(mock)} in the current process (#{inspected}) \
because the process has been allowed by #{inspect(owner_pid)}. \
You cannot define expectations/stubs in a process that has been allowed
"""
defp add_expectations!(mock, key_expectation_list) do
validate_mock!(mock)

{:error, {:not_shared_owner, global_pid}} ->
inspected = inspect(self())
case add_expectations(self(), mock, key_expectation_list) do
:ok ->
:ok

raise ArgumentError, """
cannot add expectations/stubs to #{inspect(mock)} in the current process (#{inspected}) \
because Mox is in global mode and the global process is #{inspect(global_pid)}. \
Only the process that set Mox to global can set expectations/stubs in global mode
"""
{:error, error} ->
raise ArgumentError, expectation_error_to_message(error, mock)
end
end

defp expectation_error_to_message({:currently_allowed, owner_pid}, mock) do
inspected = inspect(self())

"""
cannot add expectations/stubs to #{inspect(mock)} in the current process (#{inspected}) \
because the process has been allowed by #{inspect(owner_pid)}. \
You cannot define expectations/stubs in a process that has been allowed
"""
end

defp expectation_error_to_message({:not_shared_owner, global_pid}, mock) do
inspected = inspect(self())

"""
cannot add expectations/stubs to #{inspect(mock)} in the current process (#{inspected}) \
because Mox is in global mode and the global process is #{inspect(global_pid)}. \
Only the process that set Mox to global can set expectations/stubs in global mode
"""
end

@doc """
Allows other processes to share expectations and stubs
defined by owner process.
Expand Down Expand Up @@ -924,10 +947,10 @@ defmodule Mox do
end
end

defp add_expectation(owner_pid, {mock, _, _} = key, expectation) do
defp add_expectations(owner_pid, mock, key_expectation_list) do
case ensure_pid_can_add_expectation(owner_pid, mock) do
:ok ->
update_fun = &{:ok, init_or_merge_expectations(&1, key, expectation)}
update_fun = &{:ok, init_or_merge_expectations(&1, key_expectation_list)}
:ok = get_and_update!(owner_pid, mock, update_fun)

{:error, reason} ->
Expand Down Expand Up @@ -984,13 +1007,18 @@ defmodule Mox do
end
end

defp init_or_merge_expectations(current_exps, key, {n, calls, stub} = new_exp)
defp init_or_merge_expectations(current_exps, [{key, {n, calls, stub} = new_exp} | tail])
when is_map(current_exps) or is_nil(current_exps) do
Map.update(current_exps || %{}, key, new_exp, fn {current_n, current_calls, _current_stub} ->
{current_n + n, current_calls ++ calls, stub}
end)
init_or_merge_expectations(
Map.update(current_exps || %{}, key, new_exp, fn {current_n, current_calls, _current_stub} ->
{current_n + n, current_calls ++ calls, stub}
end),
tail
)
end

defp init_or_merge_expectations(current_exps, []), do: current_exps

defp ok_or_remote(source) when node(source) == node(), do: :ok
defp ok_or_remote(_source), do: :remote
end