Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
whatyouhide committed Jan 20, 2024
1 parent 0e13f69 commit f953af4
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 111 deletions.
20 changes: 9 additions & 11 deletions lib/mox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -731,10 +731,9 @@ defmodule Mox do
mock

{:error, %NimbleOwnership.Error{reason: :not_allowed}} ->
raise ArgumentError, """
cannot allow #{inspect(allowed_pid_or_function)} to use #{inspect(mock)} \
because the owner PID #{inspect(owner_pid)} is not allowed to use it\
"""
:ok = Mox.Server.init_mock(owner_pid, mock)
allow(mock, owner_pid, allowed_via)
mock

{:error, %NimbleOwnership.Error{reason: {:already_allowed, actual_pid}}} ->
raise ArgumentError, """
Expand All @@ -755,7 +754,7 @@ defmodule Mox do
because the process has already defined its own expectations/stubs
"""

{:error, %NimbleOwnership.Error{reason: :cant_allow_in_global_mode}} ->
{:error, %NimbleOwnership.Error{reason: :cant_allow_in_shared_mode}} ->
# Already allowed
mock
end
Expand All @@ -773,10 +772,9 @@ defmodule Mox do
@spec verify_on_exit!(term()) :: :ok
def verify_on_exit!(_context \\ %{}) do
pid = self()
Mox.Server.verify_on_exit(pid)

ExUnit.Callbacks.on_exit(Mox, fn ->
verify_mock_or_all!(pid, :all, :on_exit)
verify_mock_or_all!(pid, :all)
end)
end

Expand All @@ -786,7 +784,7 @@ defmodule Mox do
"""
@spec verify!() :: :ok
def verify! do
verify_mock_or_all!(self(), :all, :test)
verify_mock_or_all!(self(), :all)
end

@doc """
Expand All @@ -795,11 +793,11 @@ defmodule Mox do
@spec verify!(t()) :: :ok
def verify!(mock) do
validate_mock!(mock)
verify_mock_or_all!(self(), mock, :test)
verify_mock_or_all!(self(), mock)
end

defp verify_mock_or_all!(pid, mock, test_or_on_exit) do
pending = Mox.Server.verify(pid, mock, test_or_on_exit)
defp verify_mock_or_all!(pid, mock) do
pending = Mox.Server.verify(pid, mock)

messages =
for {{module, name, arity}, total, pending} <- pending do
Expand Down
148 changes: 51 additions & 97 deletions lib/mox/server.ex
Original file line number Diff line number Diff line change
@@ -1,152 +1,106 @@
defmodule Mox.Server do
@moduledoc false

use GenServer
alias NimbleOwnership, as: N

@timeout 30000
@this {:global, __MODULE__}

# API

def child_spec(_options) do
%{id: __MODULE__, start: {__MODULE__, :start_ownership_server_link, []}}
%{id: __MODULE__, start: {__MODULE__, :start_link_ownership, []}}
end

def start_ownership_server_link do
case NimbleOwnership.start_link(name: @this) do
def start_link_ownership do
case N.start_link(name: @this) do
{:error, {:already_started, _}} -> :ignore
other -> other
end
end

def add_expectation(owner_pid, {mock, _, _} = key, expectation) do
case NimbleOwnership.fetch_owner(@this, [owner_pid], mock) do
{:ok, ^owner_pid} ->
:ok

{:ok, other_owner} ->
throw({:error, {:currently_allowed, other_owner}})

:error ->
:ok
# First, make sure that the owner_pid is either the owner or that the mock
# isn't owned yet. Otherwise, return an error.
case N.fetch_owner(@this, [owner_pid], mock, @timeout) do
{tag, ^owner_pid} when tag in [:ok, :global_owner] -> :ok
{:global_owner, other_owner} -> throw({:error, {:not_global_owner, other_owner}})
{:ok, other_owner} -> throw({:error, {:currently_allowed, other_owner}})
:error -> :ok
end

{:ok, :ok} =
NimbleOwnership.get_and_update(@this, owner_pid, mock, fn
nil ->
{:ok, %{key => expectation}}
update_fun = fn
nil ->
{nil, %{key => expectation}}

%{} = expectations ->
{:ok, Map.update(expectations, key, expectation, &merge_expectation(&1, expectation))}
end)
%{} = expectations ->
{nil, Map.update(expectations, key, expectation, &merge_expectation(&1, expectation))}
end

{:ok, _} = N.get_and_update(@this, owner_pid, mock, update_fun, @timeout)
:ok
catch
return -> return
{:error, reason} -> {:error, reason}
end

def init_mock(owner_pid, mock) do
{:ok, _} = N.get_and_update(@this, owner_pid, mock, fn nil -> {nil, %{}} end, @timeout)
:ok
end

def fetch_fun_to_dispatch(caller_pids, {mock, _, _} = key) do
# If the mock doesn't have an owner, it can't have expectations so we return :no_expectation.
owner_pid =
case NimbleOwnership.fetch_owner(@this, caller_pids, mock) do
{:ok, owner_pid} -> owner_pid
case N.fetch_owner(@this, caller_pids, mock, @timeout) do
{tag, owner_pid} when tag in [:global_owner, :ok] -> owner_pid
:error -> throw(:no_expectation)
end

{:ok, return} =
NimbleOwnership.get_and_update(@this, owner_pid, mock, fn expectations ->
case expectations[key] do
nil ->
{:no_expectation, expectations}
parent = self()

{total, [], nil} ->
{{:out_of_expectations, total}, expectations}
update_fun = fn expectations ->
case expectations[key] do
nil ->
{:no_expectation, expectations}

{_, [], stub} ->
{{ok_or_remote(self()), stub}, expectations}
{total, [], nil} ->
{{:out_of_expectations, total}, expectations}

{total, [call | calls], stub} ->
new_expectations = put_in(expectations[key], {total, calls, stub})
{{ok_or_remote(self()), call}, new_expectations}
end
end)
{_, [], stub} ->
{{ok_or_remote(parent), stub}, expectations}

{total, [call | calls], stub} ->
new_expectations = put_in(expectations[key], {total, calls, stub})
{{ok_or_remote(parent), call}, new_expectations}
end
end

{:ok, return} = N.get_and_update(@this, owner_pid, mock, update_fun, @timeout)
return
catch
return -> return
end

def verify(owner_pid, for, test_or_on_exit) do
expectations = NimbleOwnership.get_owned(@this, owner_pid, %{})
def verify(owner_pid, for) do
all_expectations = N.get_owned(@this, owner_pid, _default = %{}, @timeout)

pending =
for {_mock, expected_funs} <- expectations,
_pending =
for {_mock, expected_funs} <- all_expectations,
{{module, _, _} = key, {count, [_ | _] = calls, _stub}} <- expected_funs,
module == for or for == :all do
{key, count, length(calls)}
end

# state =
# if test_or_on_exit == :on_exit do
# down(state, owner_pid)
# else
# state
# end

pending
end

def verify_on_exit(pid) do
# raise "TODO"
:ok
# GenServer.call(@this, {:verify_on_exit, pid}, @timeout)
end

def allow(mock, owner_pid, pid_or_function) do
NimbleOwnership.allow(@this, owner_pid, pid_or_function, mock)
N.allow(@this, owner_pid, pid_or_function, mock, @timeout)
end

def set_mode(_owner_pid, :private), do: NimbleOwnership.set_mode_to_private(@this)
def set_mode(owner_pid, :global), do: NimbleOwnership.set_mode_to_global(@this, owner_pid)

# Callbacks

# def handle_call(msg, _from, state) do
# # The global process may have terminated and we did not receive
# # the DOWN message yet, so we always check accordingly if it is alive.
# with %{mode: :global, global_owner_pid: global_owner_pid} <- state,
# false <- Process.alive?(global_owner_pid) do
# handle_call(msg, reset_global_mode(state))
# else
# _ -> handle_call(msg, state)
# end
# end
def set_mode(_owner_pid, :private), do: N.set_mode_to_private(@this)
def set_mode(owner_pid, :global), do: N.set_mode_to_shared(@this, owner_pid)

# Helper functions

defp reset_global_mode(state) do
%{state | mode: :private, global_owner_pid: nil}
end

defp down(state, pid) do
{_, state} = pop_in(state.expectations[pid])
state
end

defp maybe_add_and_monitor_pid(state, pid) do
maybe_add_and_monitor_pid(state, pid, :DOWN, & &1)
end

defp maybe_add_and_monitor_pid(state, pid, on, fun) do
case state.deps do
%{^pid => entry} ->
put_in(state.deps[pid], fun.(entry))

_ ->
Process.monitor(pid)
state = put_in(state.deps[pid], {on, []})
state
end
end

defp merge_expectation({current_n, current_calls, _current_stub}, {n, calls, stub}) do
{current_n + n, current_calls ++ calls, stub}
end
Expand Down
3 changes: 1 addition & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ defmodule Mox.MixProject do

defp deps do
[
# {:nimble_ownership, github: "dashbitco/nimble_ownership", branch: "al/rework-api"},
{:nimble_ownership, path: "~/Code/nimble_ownership"},
{:nimble_ownership, github: "dashbitco/nimble_ownership", branch: "al/global-mode"},
{:ex_doc, "~> 0.16", only: :docs}
]
end
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_ownership": {:git, "https://github.com/dashbitco/nimble_ownership.git", "d343de2babb8c56ee3ac4272b8ad5485af0db120", [branch: "al/rework-api"]},
"nimble_ownership": {:git, "https://github.com/dashbitco/nimble_ownership.git", "2820277da2386b749863ab59c773ce747bfade7b", [branch: "al/global-mode"]},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
}

0 comments on commit f953af4

Please sign in to comment.