Packages

tddef helps you do test driven development in Elixir

Current section

Files

Jump to
tddef lib check function_call.ex
Raw

lib/check/function_call.ex

defmodule Tddef.Check.FunctionCall do
@moduledoc """
Check that the function defined by def is called in a test.
"""
def check!(
caller,
{:when, _,
[
{name, _, args},
_
]},
meta
) do
check!(caller, {name, [], args}, meta)
end
def check!(caller, {fn_name, _, args}, meta) do
arity = length(args)
test_file = meta |> Keyword.fetch!(:test_files) |> hd()
with {:ok, contents} <- File.read(test_file),
{:ok, ast} <- Code.string_to_quoted(contents, file: test_file),
true <- function_called_in_ast?(ast, caller.module, fn_name, arity) do
:ok
else
_ ->
raise(
"Can't find a function call of #{caller.module}.#{fn_name}/#{arity} in #{test_file}.\nCreate a test for the function in the test file."
)
end
end
def function_called_in_ast?(ast, module_name, fn_name, arity) do
ast
|> find(module_name, fn_name, arity)
|> Enum.any?(&(&1 == true))
end
# The AST uses a tuple to represent the basic building building block, the AST node.
# An AST node always has the following format:
# {marker, metadata, children}
# The marker is some kind of atom,
# the metadata is a keyword list which contains annotations about the metadata node (line number, file, optional column number, et cetera).
# The children is either a list of child AST nodes or it’s an atom.
defp find({fun, _metadata, args}, _module_name, fn_name, arity) when fun == fn_name do
[length(args) == arity]
end
defp find(
{{:., _, [{:__aliases__, _, modules}, fun]}, _, args},
module_name,
fn_name,
arity
)
when fun == fn_name and is_list(modules) do
last_expected_module = Module.split(module_name) |> Enum.reverse() |> hd() |> String.to_atom()
matching_module? =
modules
|> Enum.reverse()
|> hd()
|> Kernel.==(last_expected_module)
actual_arity = length(args)
[matching_module? and actual_arity == arity]
end
defp find({_marker, _metadata, children}, module_name, fn_name, arity) when is_list(children) do
children
|> Enum.flat_map(fn child_ast -> find(child_ast, module_name, fn_name, arity) end)
end
defp find(ast, module_name, fn_name, arity) when is_list(ast) do
Enum.flat_map(ast, fn sub_ast -> find(sub_ast, module_name, fn_name, arity) end)
end
defp find({_marker, ast}, module_name, fn_name, arity) do
find(ast, module_name, fn_name, arity)
end
defp find(_, _module_name, _fn_name, _arity), do: []
end