Current section
Files
Jump to
Current section
Files
lib/assertions.ex
defmodule Assertions do
@moduledoc """
Helpful assertions with great error messages to help you write better tests.
"""
alias Assertions.Comparisons
@type comparison :: (any, any -> boolean | no_return)
@doc """
Asserts that the return value of the given expression is `true`.
This is different than the normal behavior of `assert` since that will pass
for any value that is "truthy" (anything other than `false` or `nil`). This is
a stricter assertion, only passing if the value is `true`. This is very
helpful for testing values that are expected to only be booleans.
This will also check specifically for `nil` values when using `>`, `<`, `>=`
or `<=` since those frequently have unintended behavior.
iex> assert!(:a == :a)
true
iex> assert!(10 > 5)
true
iex> map = %{key: true}
iex> assert!(map.key)
true
"""
@spec assert!(Macro.expr()) :: true | no_return
defmacro assert!({operator, _, [left, right]} = assertion)
when operator in [:>, :<, :>=, :<=] do
expr = escape_quoted(:assert!, assertion)
{args, value} = extract_args(assertion, __CALLER__)
quote do
left = unquote(left)
right = unquote(right)
if is_nil(left) or is_nil(right) do
assert false,
left: left,
right: right,
expr: unquote(expr),
message:
"`nil` is not allowed as an argument to `#{unquote(operator)}` when using `assert!`"
else
value = unquote(value)
unless value == true do
raise ExUnit.AssertionError,
args: unquote(args),
expr: unquote(expr),
message: "Expected `true`, got #{inspect(value)}"
end
true
end
end
end
defmacro assert!(assertion) do
{args, value} = extract_args(assertion, __CALLER__)
quote do
value = unquote(value)
unless value == true do
raise ExUnit.AssertionError,
args: unquote(args),
expr: unquote(escape_quoted(:assert!, assertion)),
message: "Expected `true`, got #{inspect(value)}"
end
value
end
end
@doc """
Asserts that the return value of the given expression is `false`.
This is different than the normal behavior of `refute/1` since that will pass
if the value is either `false` or `nil`. This is a stricter assertion, only
passing if the value is `false`. This is very helpful for testing values that
are expected to only be booleans.
This will also check specifically for `nil` values when using `>`, `<`, `>=`
or `<=` since those frequently have unintended behavior.
iex> refute!(5 > 10)
true
iex> refute!("a" == "A")
true
"""
@spec refute!(Macro.expr()) :: true | no_return
defmacro refute!({operator, _, [left, right]} = assertion)
when operator in [:>, :<, :>=, :<=] do
expr = escape_quoted(:refute!, assertion)
{args, value} = extract_args(assertion, __CALLER__)
quote do
left = unquote(left)
right = unquote(right)
if is_nil(left) or is_nil(right) do
raise ExUnit.AssertionError,
args: unquote(args),
expr: unquote(expr),
left: left,
right: right,
message:
"`nil` is not allowed as an argument to `#{unquote(operator)}` when using `refute!`"
else
value = unquote(value)
unless value == false do
raise ExUnit.AssertionError,
args: unquote(args),
expr: unquote(expr),
left: left,
right: right,
message: "Expected `false`, got #{inspect(value)}"
end
true
end
end
end
defmacro refute!(assertion) do
{args, value} = extract_args(assertion, __CALLER__)
quote do
value = unquote(value)
unless value == false do
raise ExUnit.AssertionError,
args: unquote(args),
expr: unquote(escape_quoted(:refute!, assertion)),
message: "Expected `false`, got #{inspect(value)}"
end
true
end
end
@doc """
Asserts that two lists contain the same elements without asserting they are
in the same order.
iex> assert_lists_equal([1, 2, 3], [1, 3, 2])
true
"""
@spec assert_lists_equal(list, list) :: true | no_return
defmacro assert_lists_equal(left, right) do
assertion =
assertion(
quote do
assert_lists_equal(unquote(left), unquote(right))
end
)
quote do
{left_diff, right_diff, equal?} = Comparisons.compare_lists(unquote(left), unquote(right))
if equal? do
true
else
raise ExUnit.AssertionError,
args: [unquote(left), unquote(right)],
left: left_diff,
right: right_diff,
expr: unquote(assertion),
message: "Comparison of each element failed!"
end
end
end
@doc """
Asserts that two lists contain the same elements without asserting they are
in the same order.
The given comparison function determines if the two lists are considered
equal.
iex> assert_lists_equal(["dog"], ["cat"], &(is_binary(&1) and is_binary(&2)))
true
"""
@spec assert_lists_equal(list, list, comparison) :: true | no_return
defmacro assert_lists_equal(left, right, comparison) do
assertion =
assertion(
quote do
assert_lists_equal(unquote(left), unquote(right), unquote(comparison))
end
)
quote do
{left_diff, right_diff, equal?} =
Comparisons.compare_lists(unquote(left), unquote(right), unquote(comparison))
if equal? do
true
else
raise ExUnit.AssertionError,
args: [unquote(left), unquote(right), unquote(comparison)],
left: left_diff,
right: right_diff,
expr: unquote(assertion),
message: "Comparison of each element failed!"
end
end
end
@doc """
Asserts that a `map` is in the given `list`.
This is either done by passing a list of `keys`, and the values at those keys
will be compared to determine if the map is in the list.
iex> map = %{first: :first, second: :second}
iex> list = [%{first: :first, second: :second, third: :third}]
iex> keys = [:first, :second]
iex> assert_map_in_list(map, list, keys)
true
Or this is done by passing a comparison function that determines if the map
is in the list.
If using a comparison function, the `map` is the first argument to that
function, and the elements in the list are the second argument.
iex> map = %{first: :first, second: :second}
iex> list = [%{"first" => :first, "second" => :second, "third" => :third}]
iex> comparison = &(&1.first == &2["first"] and &1.second == &2["second"])
iex> assert_map_in_list(map, list, comparison)
true
"""
@spec assert_map_in_list(map, [map], [any]) :: true | no_return
@spec assert_map_in_list(map, [map], comparison) :: true | no_return
defmacro assert_map_in_list(map, list, keys_or_comparison) do
assertion =
assertion(
quote do
assert_map_in_list(unquote(map), unquote(list), unquote(keys_or_comparison))
end
)
quote do
keys_or_comparison = unquote(keys_or_comparison)
{in_list?, map, list, message} =
if is_list(keys_or_comparison) do
keys = keys_or_comparison
map = Map.take(unquote(map), keys)
list = Enum.map(unquote(list), &Map.take(&1, keys))
keys = unquote(stringify_list(keys_or_comparison))
message = "Map matching the values for keys `#{keys}` not found"
{Enum.member?(list, map), map, list, message}
else
comparison = keys_or_comparison
map = unquote(map)
list = unquote(list)
message = "Map not found in list using given comparison"
{Enum.any?(list, &comparison.(map, &1)), map, list, message}
end
if in_list? do
true
else
raise ExUnit.AssertionError,
args: [unquote(map), unquote(list)],
left: map,
right: list,
expr: unquote(assertion),
message: message
end
end
end
@doc """
Asserts that two maps are equal.
Equality can be determined in two ways. First, by passing a list of keys. The
values at these keys will be used to determine if the maps are equal.
iex> left = %{first: :first, second: :second, third: :third}
iex> right = %{first: :first, second: :second, third: :fourth}
iex> keys = [:first, :second]
iex> assert_maps_equal(left, right, keys)
true
The second is to pass a comparison function that returns a boolean that
determines if the maps are equal. When using a comparison function, the first
argument to the function is the `left` map and the second argument is the
`right` map.
iex> left = %{first: :first, second: :second, third: :third}
iex> right = %{"first" => :first, "second" => :second, "third" => :fourth}
iex> comparison = &(&1.first == &2["first"] and &1.second == &2["second"])
iex> assert_maps_equal(left, right, comparison)
true
"""
@spec assert_maps_equal(map, map, [any]) :: true | no_return
@spec assert_maps_equal(map, map, comparison) :: true | no_return
defmacro assert_maps_equal(left, right, keys_or_comparison) do
assertion =
assertion(
quote do
assert_maps_equal(unquote(left), unquote(right), unquote(keys_or_comparison))
end
)
quote do
keys_or_comparison = unquote(keys_or_comparison)
left = unquote(left)
right = unquote(right)
{left_diff, right_diff, equal?, message} =
if is_list(keys_or_comparison) do
keys = keys_or_comparison
left = Map.take(left, keys)
right = Map.take(right, keys)
{left_diff, right_diff, equal?} = Comparisons.compare_maps(left, right)
message = "Values for #{unquote(stringify_list(keys_or_comparison))} not equal!"
{left_diff, right_diff, equal?, message}
else
comparison = keys_or_comparison
{left, right, comparison.(left, right), "Maps not equal using given comprison"}
end
if equal? do
true
else
raise ExUnit.AssertionError,
args: [unquote(left), unquote(right)],
left: left_diff,
right: right_diff,
expr: unquote(assertion),
message: message
end
end
end
@doc """
Asserts that the `struct` is present in the `list`.
There are two ways to make this comparison. First is to pass a list of keys
to use to compare the `struct` to the structs in the `list`.
iex> now = DateTime.utc_now()
iex> list = [DateTime.utc_now(), Date.utc_today()]
iex> keys = [:year, :month, :day]
iex> assert_struct_in_list(now, list, keys)
true
The second way to use this assertion is to pass a comparison function.
When using a comparison function, the `struct` is the first argument to that
function and the elements in the `list` will be the second argument.
iex> now = DateTime.utc_now()
iex> list = [DateTime.utc_now(), Date.utc_today()]
iex> assert_struct_in_list(now, list, &(&1.year == &2.year))
true
"""
@spec assert_struct_in_list(struct, [struct], [atom]) :: true | no_return
@spec assert_struct_in_list(struct, [struct], (struct, struct -> boolean)) :: true | no_return
defmacro assert_struct_in_list(struct, list, keys_or_comparison) do
assertion =
assertion(
quote do
assert_struct_in_list(unquote(struct), unquote(list), unquote(keys_or_comparison))
end
)
quote do
struct = unquote(struct)
list = unquote(list)
keys_or_comparison = unquote(keys_or_comparison)
{in_list?, message} =
if is_list(keys_or_comparison) do
keys = [:__struct__ | keys_or_comparison]
struct = Map.take(struct, keys)
list = Enum.map(list, &Map.take(&1, keys))
{struct in list,
"Struct matching the values for keys #{unquote(stringify_list(keys_or_comparison))} not found"}
else
comparison = keys_or_comparison
{Enum.any?(list, &comparison.(struct, &1)),
"Struct not found in list using the given comparison"}
end
if in_list? do
true
else
raise ExUnit.AssertionError,
args: [struct, list, keys_or_comparison],
left: struct,
right: list,
expr: unquote(assertion),
message: message
end
end
end
@doc """
Asserts that two structs are equal.
Equality can be determined in two ways. First, by passing a list of keys. The
values at these keys and the type of the structs will be used to determine if
the structs are equal.
iex> left = DateTime.utc_now()
iex> right = DateTime.utc_now()
iex> keys = [:year, :minute]
iex> assert_structs_equal(left, right, keys)
true
The second is to pass a comparison function that returns a boolean that
determines if the structs are equal. When using a comparison function, the
first argument to the function is the `left` struct and the second argument
is the `right` struct.
iex> left = DateTime.utc_now()
iex> right = DateTime.utc_now()
iex> comparison = &(&1.year == &2.year and &1.minute == &2.minute)
iex> assert_structs_equal(left, right, comparison)
true
"""
@spec assert_structs_equal(struct, struct, [atom]) :: true | no_return
@spec assert_structs_equal(struct, struct, (any, any -> boolean)) :: true | no_return
defmacro assert_structs_equal(left, right, keys_or_comparison) do
assertion =
assertion(
quote do
assert_structs_equal(unquote(left), unquote(right), unquote(keys_or_comparison))
end
)
quote do
left = unquote(left)
right = unquote(right)
keys_or_comparison = unquote(keys_or_comparison)
{left_diff, right_diff, equal?, message} =
if is_list(keys_or_comparison) do
keys = [:__struct__ | keys_or_comparison]
left = Map.take(left, keys)
right = Map.take(right, keys)
message = "Values for #{unquote(stringify_list(keys_or_comparison))} not equal!"
{left_diff, right_diff, equal?} = Comparisons.compare_maps(left, right)
{left_diff, right_diff, equal?, message}
else
comparison = keys_or_comparison
{left_diff, right_diff, equal?} =
case comparison.(left, right) do
{_, _, equal?} = result when is_boolean(equal?) -> result
true_or_false when is_boolean(true_or_false) -> {left, right, true_or_false}
end
{left_diff, right_diff, equal?, "Comparison failed!"}
end
if equal? do
true
else
raise ExUnit.AssertionError,
args: [unquote(left), unquote(right)],
left: left_diff,
right: right_diff,
expr: unquote(assertion),
message: message
end
end
end
@doc """
Asserts that all maps, structs or keyword lists in `list` have the same
`value` for `key`.
iex> assert_all_have_value([%{key: :value}, %{key: :value, other: :key}], :key, :value)
true
iex> assert_all_have_value([[key: :value], [key: :value, other: :key]], :key, :value)
true
iex> assert_all_have_value([[key: :value], %{key: :value, other: :key}], :key, :value)
true
"""
@spec assert_all_have_value(list(map | struct | Keyword.t()), any, any) :: true | no_return
defmacro assert_all_have_value(list, key, value) do
assertion =
assertion(
quote do
assert_all_have_value(unquote(list), unquote(key), unquote(value))
end
)
quote do
key = unquote(key)
value = unquote(value)
list =
Enum.map(unquote(list), fn
map when is_map(map) -> Map.take(map, [key])
list -> [{key, Keyword.get(list, key, :key_not_present)}]
end)
diff =
Enum.reject(list, fn
map when is_map(map) -> Map.equal?(map, %{key => value})
list -> Keyword.equal?(list, [{key, value}])
end)
if diff == [] do
true
else
raise ExUnit.AssertionError,
args: [unquote(list), unquote(key), unquote(value)],
left: %{key => value},
right: diff,
expr: unquote(assertion),
message: "Values for `#{inspect(key)}` not equal in all elements!"
end
end
end
@doc """
Asserts that the file at `path` is changed to match `comparison` after
executing the given `expression`.
If the file matches `comparison` before executing `expr`, this assertion will
fail. The file does not have to exist before executing `expr` in order for
this assertion to pass.
iex> path = Path.expand("../tmp/file.txt", __DIR__)
iex> result = assert_changes_file(path, "hi") do
iex> File.mkdir_p!(Path.dirname(path))
iex> File.write(path, "hi")
iex> end
iex> File.rm_rf!(Path.dirname(path))
iex> result
true
"""
@spec assert_changes_file(Path.t(), String.t() | Regex.t(), Macro.expr()) :: true | no_return
defmacro assert_changes_file(path, comparison, [do: expr] = expression) do
assertion =
assertion(
quote do
assert_changes_file(unquote(path), unquote(comparison), unquote(expression))
end
)
quote do
path = unquote(path)
comparison = unquote(comparison)
args = [unquote(path), unquote(comparison)]
{match_before?, start_file} =
case File.read(path) do
{:ok, start_file} -> {start_file =~ comparison, start_file}
_ -> {false, nil}
end
if match_before? do
raise ExUnit.AssertionError,
args: args,
expr: unquote(assertion),
left: start_file,
right: unquote(comparison),
message: "File #{inspect(path)} matched `#{inspect(comparison)}` before executing expr!"
else
unquote(expr)
end_file =
case File.read(path) do
{:ok, end_file} ->
end_file
_ ->
raise ExUnit.AssertionError,
args: args,
expr: unquote(assertion),
message: "File #{inspect(path)} does not exist after executing expr!"
end
if end_file =~ comparison do
true
else
raise ExUnit.AssertionError,
args: args,
left: end_file,
right: comparison,
expr: unquote(assertion),
message: "File did not change to match comparison after expr!"
end
end
end
end
@doc """
Asserts that the file at `path` is created after executing the given
`expression`.
iex> path = Path.expand("../tmp/file.txt", __DIR__)
iex> File.mkdir_p!(Path.dirname(path))
iex> result = assert_creates_file path do
iex> File.write(path, "hi")
iex> end
iex> File.rm_rf!(Path.dirname(path))
iex> result
true
"""
@spec assert_creates_file(Path.t(), Macro.expr()) :: true | no_return
defmacro assert_creates_file(path, [do: expr] = expression) do
assertion =
assertion(
quote do
assert_creates_file(unquote(path), unquote(expression))
end
)
quote do
path = unquote(path)
args = [unquote(path)]
if File.exists?(path) do
raise ExUnit.AssertionError,
args: args,
expr: unquote(assertion),
message: "File #{inspect(path)} existed before executing expr!"
else
unquote(expr)
if File.exists?(path) do
true
else
raise ExUnit.AssertionError,
args: args,
expr: unquote(assertion),
message: "File #{inspect(path)} does not exist after executing expr!"
end
end
end
end
@doc """
Asserts that the file at `path` is deleted after executing the given
`expression`.
iex> path = Path.expand("../tmp/file.txt", __DIR__)
iex> File.mkdir_p!(Path.dirname(path))
iex> File.write(path, "hi")
iex> assert_deletes_file path do
iex> File.rm_rf!(Path.dirname(path))
iex> end
true
"""
@spec assert_deletes_file(Path.t(), Macro.expr()) :: true | no_return
defmacro assert_deletes_file(path, [do: expr] = expression) do
assertion =
assertion(
quote do
assert_deletes_file(unquote(path), unquote(expression))
end
)
quote do
path = unquote(path)
args = [unquote(path)]
if !File.exists?(path) do
raise ExUnit.AssertionError,
args: args,
expr: unquote(assertion),
message: "File #{inspect(path)} did not exist before executing expr!"
else
unquote(expr)
if !File.exists?(path) do
true
else
raise ExUnit.AssertionError,
args: args,
expr: unquote(assertion),
message: "File #{inspect(path)} exists after executing expr!"
end
end
end
end
@doc """
Tests that a message matching the given `pattern`, and only that message, is
received before the given `timeout`, specified in milliseconds.
The optional second argument is a timeout for the `receive` to wait for the
expected message, and defaults to 100ms.
## Examples
iex> send(self(), :hello)
iex> assert_receive_only(:hello)
true
iex> send(self(), [:hello])
iex> assert_receive_only([_])
true
iex> a = :hello
iex> send(self(), :hello)
iex> assert_receive_only(^a)
true
iex> send(self(), :hello)
iex> assert_receive_only(a when is_atom(a))
iex> a
:hello
iex> send(self(), %{key: :value})
iex> assert_receive_only(%{key: value} when is_atom(value))
iex> value
:value
If a message is received after the assertion has matched a message to the
given pattern, but the second message is received before the timeout, that
second message is ignored and the assertion returns `true`.
This assertion only tests that the message that matches the given pattern was
the first message in the process inbox, and that nothing was sent between the
sending the message that matches the pattern and when `assert_receive_only/2`
was called.
iex> Process.send_after(self(), :hello, 20)
iex> Process.send_after(self(), :hello_again, 50)
iex> assert_receive_only(:hello, 100)
true
"""
@spec assert_receive_only(Macro.expr(), non_neg_integer) :: any | no_return
defmacro assert_receive_only(pattern, timeout \\ 100) do
binary = Macro.to_string(pattern)
assertion =
assertion(
quote do
assert_receive_only(unquote(pattern), unquote(timeout))
end
)
caller = __CALLER__
# Expand before extracting metadata
pattern = expand_pattern(pattern, caller)
vars = collect_vars_from_pattern(pattern)
pins = collect_pins_from_pattern(pattern, Macro.Env.vars(caller))
pattern =
case pattern do
{:when, meta, [left, right]} ->
{:when, meta, [quote(do: unquote(left) = received), right]}
left ->
quote(do: unquote(left) = received)
end
quoted_pattern =
quote do
case message do
unquote(pattern) ->
_ = unquote(vars)
true
_ ->
false
end
end
pattern_finder =
quote do
fn message ->
unquote(suppress_warning(quoted_pattern))
end
end
timeout =
if is_integer(timeout) do
timeout
else
quote do: ExUnit.Assertions.__timeout__(unquote(timeout))
end
failure_message =
quote do
ExUnit.Assertions.__timeout__(
unquote(binary),
unquote(pins),
unquote(pattern_finder),
timeout
)
end
bind_variables =
quote do
{received, unquote(vars)}
end
quote do
timeout = unquote(timeout)
unquote(bind_variables) =
receive do
unquote(pattern) ->
result = unquote(bind_variables)
receive do
thing ->
raise ExUnit.AssertionError,
expr: unquote(assertion),
message: "`#{inspect(thing)}` was also in the mailbox"
after
0 ->
result
end
random_thing ->
raise ExUnit.AssertionError,
expr: unquote(assertion),
message: "Received unexpected message: `#{inspect(random_thing)}`"
after
timeout -> flunk(unquote(failure_message))
end
true
end
end
@doc """
Asserts that some condition succeeds within a given timeout (in milliseconds)
and sleeps for a given time between checks of the given condition (in
milliseconds).
This is helpful for testing that asynchronous operations have succeeded within
a certain timeframe. This method of testing asynchronous operations is less
reliable than other methods, but it can often be more useful at an integration
level.
iex> Process.send_after(self(), :hello, 50)
iex> assert_async do
iex> assert_received :hello
iex> end
true
iex> Process.send_after(self(), :hello, 50)
iex> assert_async(timeout: 75, sleep_time: 40) do
iex> assert_received :hello
iex> end
true
iex> Process.send_after(self(), :hello, 50)
iex> try do
iex> assert_async(timeout: 4, sleep_time: 2) do
iex> assert_received :hello
iex> end
iex> rescue
iex> _ -> :failed
iex> end
:failed
"""
@spec assert_async(Keyword.t(), Macro.expr()) :: true | no_return
defmacro assert_async(opts \\ [], [do: expr] = expression) do
sleep_time = Keyword.get(opts, :sleep_time, 10)
timeout = Keyword.get(opts, :timeout, 100)
assertion =
assertion(
quote do
assert_async(unquote(opts), unquote(expression))
end
)
condition =
quote do
fn -> unquote(expr) end
end
quote do
assert_async(unquote(condition), unquote(assertion), unquote(timeout), unquote(sleep_time))
end
end
@doc false
def assert_async(condition, expr, timeout, sleep_time) do
start_time = NaiveDateTime.utc_now()
end_time = NaiveDateTime.add(start_time, timeout, :millisecond)
assert_async(condition, end_time, expr, timeout, sleep_time)
end
@doc false
def assert_async(condition, end_time, expr, timeout, sleep_time) do
result =
try do
condition.()
rescue
_ in [ExUnit.AssertionError] -> false
end
if result == false do
if NaiveDateTime.compare(NaiveDateTime.utc_now(), end_time) == :lt do
Process.sleep(sleep_time)
assert_async(condition, end_time, expr, timeout, sleep_time)
else
raise ExUnit.AssertionError,
args: [timeout],
expr: expr,
message: "Given condition did not return true before timeout: #{timeout}"
end
else
true
end
end
defp assertion(quoted), do: Macro.escape(quoted, prune_metadata: true)
defp stringify_list(list) do
quote do
unquote(list)
|> Enum.map(fn
elem when is_atom(elem) -> ":#{elem}"
elem when is_binary(elem) -> "\"#{elem}\""
elem -> "#{inspect(elem)}"
end)
|> Enum.join(", ")
end
end
defp expand_pattern({:when, meta, [left, right]}, caller) do
left = expand_pattern_except_vars(left, Macro.Env.to_match(caller))
right = expand_pattern_except_vars(right, %{caller | context: :guard})
{:when, meta, [left, right]}
end
defp expand_pattern(expr, caller) do
expand_pattern_except_vars(expr, Macro.Env.to_match(caller))
end
defp expand_pattern_except_vars(expr, caller) do
Macro.prewalk(expr, fn
{var, _, context} = node when is_atom(var) and is_atom(context) -> node
other -> Macro.expand(other, caller)
end)
end
defp collect_vars_from_pattern(expr) do
Macro.prewalk(expr, [], fn
{:::, _, [left, _]}, acc ->
{[left], acc}
{skip, _, [_]}, acc when skip in [:^, :@] ->
{:ok, acc}
{:_, _, context}, acc when is_atom(context) ->
{:ok, acc}
{name, meta, context}, acc when is_atom(name) and is_atom(context) ->
{:ok, [{name, [generated: true] ++ meta, context} | acc]}
node, acc ->
{node, acc}
end)
|> elem(1)
end
defp collect_pins_from_pattern(expr, vars) do
{_, pins} =
Macro.prewalk(expr, [], fn
{:^, _, [{name, _, nil} = var]}, acc ->
if {name, nil} in vars do
{:ok, [{name, var} | acc]}
else
{:ok, acc}
end
form, acc ->
{form, acc}
end)
Enum.uniq_by(pins, &elem(&1, 0))
end
defp suppress_warning({name, meta, [expr, [do: clauses]]}) do
clauses =
Enum.map(clauses, fn {:->, meta, args} ->
{:->, [generated: true] ++ meta, args}
end)
{name, meta, [expr, [do: clauses]]}
end
defp extract_args({root, meta, [_ | _] = args} = expr, env) do
arity = length(args)
reserved? =
is_atom(root) and (Macro.special_form?(root, arity) or Macro.operator?(root, arity))
all_quoted_literals? = Enum.all?(args, &Macro.quoted_literal?/1)
case Macro.expand_once(expr, env) do
^expr when not reserved? and not all_quoted_literals? ->
vars = for i <- 1..arity, do: Macro.var(:"arg#{i}", __MODULE__)
quoted =
quote do
{unquote_splicing(vars)} = {unquote_splicing(args)}
unquote({root, meta, vars})
end
{vars, quoted}
other ->
{ExUnit.AssertionError.no_value(), other}
end
end
defp extract_args(expr, _env) do
{ExUnit.AssertionError.no_value(), expr}
end
defp escape_quoted(kind, expr) do
Macro.escape({kind, [], [expr]}, prune_metadata: true)
end
end