Packages
double_down
0.63.1
0.69.0
0.68.0
0.66.0
0.65.0
0.64.1
0.64.0
0.63.3
0.63.2
0.63.1
0.63.0
0.62.1
0.61.0
0.60.4
0.60.3
0.60.2
0.60.1
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.0
0.52.3
0.52.2
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.1
0.48.0
0.47.2
0.47.1
0.47.0
0.46.3
0.46.2
0.46.1
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.1
0.41.0
0.40.0
0.39.0
0.38.0
0.37.2
0.37.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.31.0
0.30.1
0.30.0
0.29.0
0.28.1
0.28.0
0.27.0
0.26.0
0.24.0
Builds on the Mox pattern — generates behaviours and dispatch facades from `defcallback` declarations — and adds stateful test doubles powerful enough to test Ecto.Repo operations without a database.
Current section
Files
Jump to
Current section
Files
lib/double_down/dynamic_facade/shim.ex
defmodule DoubleDown.DynamicFacade.Shim do
@moduledoc false
@doc false
def create_shim(module, functions, struct_info, behaviours, macros, backup) do
dispatch_fns =
for {name, arity} <- functions do
args = Macro.generate_arguments(arity, __MODULE__)
quote do
def unquote(name)(unquote_splicing(args)) do
DoubleDown.DynamicFacade.dispatch(
unquote(module),
unquote(name),
unquote(args)
)
end
end
end
behaviour_decls = generate_behaviours(behaviours)
struct_decl = generate_struct(struct_info, backup)
macro_decls = generate_macros(macros, backup)
contents = behaviour_decls ++ struct_decl ++ dispatch_fns ++ macro_decls
prev = Code.compiler_options(ignore_module_conflict: true)
try do
Module.create(module, contents, Macro.Env.location(__ENV__))
after
Code.compiler_options(ignore_module_conflict: prev[:ignore_module_conflict])
end
end
@doc false
def get_macros(backup) do
if function_exported?(backup, :__info__, 1) do
backup.__info__(:macros)
else
[]
end
end
@doc false
def get_behaviours(backup) do
backup.module_info(:attributes)
|> Keyword.get_values(:behaviour)
|> List.flatten()
end
@doc false
def get_struct_info(backup) do
if function_exported?(backup, :__info__, 1) && backup.__info__(:struct) != nil do
backup.__info__(:struct)
end
end
# -- Private --
defp generate_macros(macros, backup) do
for {macro_name, arity} <- macros do
args = Macro.generate_arguments(arity, __MODULE__)
macro_fn = String.to_existing_atom("MACRO-#{macro_name}")
quote do
defmacro unquote(macro_name)(unquote_splicing(args)) do
apply(unquote(backup), unquote(macro_fn), [__CALLER__, unquote_splicing(args)])
end
end
end
end
defp generate_behaviours(behaviours) do
for behaviour <- behaviours do
quote do
@behaviour unquote(behaviour)
end
end
end
defp generate_struct(nil, _backup), do: []
defp generate_struct(struct_info, backup) do
struct_template = Map.from_struct(backup.__struct__())
required_fields =
for %{field: field, required: true} <- struct_info, do: field
struct_params =
for %{field: field} <- struct_info do
{field, Macro.escape(struct_template[field])}
end
enforce =
if required_fields != [] do
quote do
@enforce_keys unquote(required_fields)
end
end
defstruct_decl =
quote do
defstruct unquote(struct_params)
defoverridable __struct__: 0, __struct__: 1
end
Enum.reject([enforce, defstruct_decl], &is_nil/1)
end
end