Packages

It's your application config but simply mocked with NimbleOwnership like Mox

Current section

Files

Jump to
mox_env lib mox_env.ex
Raw

lib/mox_env.ex

defmodule MoxEnv do
@callback get(key, default) :: term | default when key: atom, default: term
defmacro __using__(opts \\ []) do
module = Keyword.fetch!(opts, :config)
quote do
@behaviour MoxEnv
def get(key, default \\ nil) do
MoxEnv.get(unquote(module), key, default)
end
def put_env(key, value) do
MoxEnv.put_env(unquote(module), key, value)
end
def allow_env(owner_pid, allowed_via) do
MoxEnv.allow_env(unquote(module), owner_pid, allowed_via)
end
def set_env_from_context(context) do
MoxEnv.set_env_from_context(context)
end
end
end
@timeout :timer.seconds(30)
@this {:global, MoxEnv.Server}
@doc false
def get(module, key, default \\ nil) do
case fetch_fun_to_dispatch([self() | caller_pids()], module, key) do
{:ok, term} ->
term
:no_expectation ->
module.get(key, default)
end
catch
:exit, {:noproc, _} ->
module.get(key, default)
end
@doc false
def put_env(module, key, value) do
add_expectation(self(), module, key, value)
end
@doc false
def allow_env(module, owner_pid, allowed_via) do
allowed_pid_or_function =
case allowed_via do
fun when is_function(fun, 0) -> fun
pid_or_name -> GenServer.whereis(pid_or_name)
end
if allowed_pid_or_function == owner_pid do
raise ArgumentError, "owner_pid and allowed_pid must be different"
end
case NimbleOwnership.allow(@this, owner_pid, allowed_pid_or_function, module, @timeout) do
:ok ->
:ok
{:error, %NimbleOwnership.Error{reason: :cant_allow_in_shared_mode}} ->
# Already allowed
:ok
{:error, reason} ->
raise reason
end
end
@doc false
def set_env_from_context(%{async: true}) do
NimbleOwnership.set_mode_to_private(@this)
end
@doc false
def set_env_from_context(_context) do
NimbleOwnership.set_mode_to_shared(@this, self())
end
@doc false
def start_link_ownership do
case NimbleOwnership.start_link(name: @this) do
{:error, {:already_started, _}} -> :ignore
other -> other
end
end
defp add_expectation(owner_pid, module, key, value) do
case get_and_update(owner_pid, module, fn(state) -> {:ok, Map.put(state || %{}, key, value)} end) do
{:ok, _value} ->
:ok
{:error, error} ->
{:error, error}
end
end
defp fetch_fun_to_dispatch(caller_pids, module, key) do
with {:ok, owner_pid} <- fetch_owner_from_callers(caller_pids, module) do
get_and_update!(owner_pid, module, fn(state) ->
case Map.fetch(state, key) do
{:ok, term} ->
{{:ok, term}, state}
:error ->
{:no_expectation, state}
end
end)
end
end
defp fetch_owner_from_callers(caller_pids, mock) do
# If the mock doesn't have an owner, it can't have expectations so we return :no_expectation.
case NimbleOwnership.fetch_owner(@this, caller_pids, mock, @timeout) do
{tag, owner_pid} when tag in [:shared_owner, :ok] -> {:ok, owner_pid}
:error -> :no_expectation
end
end
defp get_and_update(owner_pid, mock, update_fun) do
NimbleOwnership.get_and_update(@this, owner_pid, mock, update_fun, @timeout)
end
defp get_and_update!(owner_pid, mock, update_fun) do
case get_and_update(owner_pid, mock, update_fun) do
{:ok, return} -> return
{:error, %NimbleOwnership.Error{} = error} -> raise error
end
end
defp caller_pids do
case Process.get(:"$callers") do
nil -> []
pids when is_list(pids) -> pids
end
end
end