Current section
Files
Jump to
Current section
Files
lib/quick_chex.ex
defmodule QuickChex do
@moduledoc """
QuickChex is a library to do property based testing
"""
@doc false
defmacro __using__(_) do
quote do
import unquote(__MODULE__)
import QuickChex.Generators
ExUnit.plural_rule("property", "properties")
Module.register_attribute __MODULE__, :qc_properties, accumulate: true
end
end
@doc """
define a property with one generated parameter
"""
defmacro property(name, param1, do: contents) do
contents = Macro.escape(contents)
param1 = Macro.escape(param1)
quote bind_quoted: [name: name, param1: param1, contents: contents] do
@qc_properties name
func_name = "quick_chex_property_#{name}" |> String.to_atom
def unquote(func_name)(unquote(param1)) do
unquote(contents)
end
end
end
@doc """
define a property with two generated parameters
"""
defmacro property(name, param1, param2, do: contents) do
contents = Macro.escape(contents)
param1 = Macro.escape(param1)
param2 = Macro.escape(param2)
quote bind_quoted: [name: name, param1: param1, param2: param2, contents: contents] do
@qc_properties name
func_name = "quick_chex_property_#{name}" |> String.to_atom
def unquote(func_name)(unquote(param1), unquote(param2)) do
unquote(contents)
end
end
end
@doc """
check a property with the given settings
"""
defmacro check(name, settings) do
generators = Macro.escape settings[:with]
iterations = settings[:iterations] || 10
quote bind_quoted: [name: name, settings: settings, generators: generators, iterations: iterations] do
func_name = "quick_chex_property_#{name}" |> String.to_atom
if Module.defines?(__MODULE__, {func_name, length(generators)}) do
1..iterations
|> Enum.map(fn num ->
test_func_name = ExUnit.Case.register_test(__ENV__, :property,
"#{name} - iteration #{num}", [])
def unquote(test_func_name)(_) do
apply(__MODULE__, unquote(func_name), unquote(generators))
end
end)
else
test_func_name = ExUnit.Case.register_test(__ENV__, :property, name, [])
def unquote(test_func_name)(_) do
property_name = unquote(name) |> to_string
raise missing_property_error_message(property_name, @qc_properties)
end
end
end
end
def missing_property_error_message(property_name, properties) do
msg = "You are trying to check a property named :#{property_name} "
<> "but a property with such name is not defined."
{similar, _} = properties
|> Enum.map(&to_string/1)
|> Enum.map(&({&1, String.jaro_distance(&1, property_name)}))
|> Enum.max_by(fn {_, distance} -> distance end)
msg <> " Do you mean :#{similar}?"
end
end