Skip to content

Commit

Permalink
Make Mimic.copy idempotent
Browse files Browse the repository at this point in the history
  • Loading branch information
dylan-chong authored Jul 28, 2022
1 parent 845fd09 commit 43dc36b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 29 deletions.
38 changes: 15 additions & 23 deletions lib/mimic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -344,42 +344,32 @@ defmodule Mimic do
@doc """
Prepare `module` for mocking.
Ideally, don't call this function twice for the same module, but in case you do, this function
is idempotent. It will not delete any `stub`s or `expect`s that you've set up.
## Arguments:
* `module` - the name of the module to copy.
"""
@spec copy(module()) :: :ok | no_return
def copy(module) do
with {:module, module} <- Code.ensure_compiled(module),
with :ok <- ensure_module_not_copied(module),
{:module, module} <- Code.ensure_compiled(module),
:ok <- Mimic.Server.mark_to_copy(module) do
ExUnit.after_suite(fn _ -> Mimic.Server.reset(module) end)
:ok
else
{:error, :module_already_copied} ->
:ok

{:error, reason}
when reason in [:embedded, :badfile, :nofile, :on_load_failure, :unavailable] ->
raise ArgumentError, "Module #{inspect(module)} is not available"

error ->
validate_server_response(error, :copy)
end

# case Code.ensure_compiled(module) do
# {:error, _} ->
# raise ArgumentError,
# "Module #{inspect(module)} is not available"

# {:module, module} ->
# case Mimic.Server.mark_to_copy(module) do
# :ok ->
# ExUnit.after_suite(fn _ -> Mimic.Server.reset(module) end)

# error ->
# validate_server_response(error, :copy)
# end

# :ok
# end
end

@doc """
Expand Down Expand Up @@ -465,6 +455,13 @@ defmodule Mimic do
Server.get_mode()
end

defp ensure_module_not_copied(module) do
case Server.marked_to_copy?(module) do
false -> :ok
true -> {:error, :module_already_copied}
end
end

defp raise_if_not_exported_function!(module, fn_name, arity) do
unless function_exported?(module, fn_name, arity) do
raise ArgumentError, "Function #{fn_name}/#{arity} not defined for #{inspect(module)}"
Expand Down Expand Up @@ -502,11 +499,6 @@ defmodule Mimic do
"Module #{inspect(module)} has not been copied. See docs for Mimic.copy/1"
end

defp validate_server_response({:error, {:module_already_copied, module}}, :copy) do
raise ArgumentError,
"Module #{inspect(module)} has already been copied. See docs for Mimic.copy/1"
end

defp validate_server_response(_, :copy) do
raise ArgumentError,
"Failed to copy module. See docs for Mimic.copy/1"
Expand Down
15 changes: 14 additions & 1 deletion lib/mimic/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ defmodule Mimic.Server do
GenServer.call(__MODULE__, {:mark_to_copy, module}, @long_timeout)
end

@spec marked_to_copy?(module) :: boolean
def marked_to_copy?(module) do
GenServer.call(__MODULE__, {:marked_to_copy?, module}, @long_timeout)
end

def apply(module, fn_name, args) do
arity = Enum.count(args)
original_module = Mimic.Module.original(module)
Expand Down Expand Up @@ -453,8 +458,12 @@ defmodule Mimic.Server do
end
end

def handle_call({:marked_to_copy?, module}, _from, state) do
{:reply, marked_to_copy?(module, state), state}
end

def handle_call({:mark_to_copy, module}, _from, state) do
if MapSet.member?(state.modules_to_be_copied, module) do
if marked_to_copy?(module, state) do
{:reply, {:error, {:module_already_copied, module}}, state}
else
# If cover is enabled call ensure_module_copied now
Expand All @@ -475,6 +484,10 @@ defmodule Mimic.Server do
end
end

defp marked_to_copy?(module, state) do
MapSet.member?(state.modules_to_be_copied, module)
end

defp do_reset(module, state) do
case state.modules_beam[module] do
{beam, coverdata} -> Cover.clear_module_and_import_coverdata!(module, beam, coverdata)
Expand Down
24 changes: 19 additions & 5 deletions test/mimic_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -908,11 +908,25 @@ defmodule Mimic.Test do
end
end

describe "copy/1" do
test "copying the same module raises" do
assert_raise ArgumentError,
"Module Calculator has already been copied. See docs for Mimic.copy/1",
fn -> Mimic.copy(Calculator) end
describe "copy/1 with duplicates does nothing" do
setup :set_mimic_private

test "stubs still stub" do
parent_pid = self()

Mimic.copy(Calculator)
Mimic.copy(Calculator)

Calculator
|> stub(:add, fn x, y ->
send(parent_pid, {:add, x, y})
:stubbed
end)

Mimic.copy(Calculator)

assert Calculator.add(1, 2) == :stubbed
assert_receive {:add, 1, 2}
end
end
end

0 comments on commit 43dc36b

Please sign in to comment.