Hello @alfert,
Would you mind telling me why:
-
when_fail
doesn’t display anything although my test is failing ?
- why
aggregate
doesn’t display/do anything either ?
The source file:
defmodule MyProject.Leaderboard do
use GenServer
alias MyProject.Submission
@initial_state []
#
# CLIENT API
#
def start_link(name) do
GenServer.start_link(__MODULE__, @initial_state, name: name)
end
def at(leaderboard, index) do
GenServer.call(leaderboard, {:at, index})
end
def record(leaderboard, submission) do
GenServer.cast(leaderboard, {:record, submission})
end
def capacity do
Application.get_env(:myproject, :leaderboard_capacity)
end
#
# SERVER API
#
def init(state) do
{:ok, state}
end
def handle_call({:at, index}, _, submissions) do
{:reply, Enum.at(submissions, index), submissions}
end
def handle_cast({:record, submission}, submissions) do
new_submissions =
submissions
|> insert(submission)
|> Enum.take(capacity)
{:noreply, new_submissions}
end
defp insert([], a_submission) do
[a_submission]
end
defp insert([sub | rest] = submissions, a_submission) do
case Submission.compare(a_submission, sub) do
:lt -> [sub | insert(rest, a_submission)]
:eq -> [sub | insert(rest, a_submission)]
:gt -> [a_submission | submissions]
end
end
end
The test file:
defmodule MyProject.LeaderboardProperty do
use ExUnit.Case, async: true
use PropCheck
use PropCheck.StateM
alias MyProject.Submission
@usernames ~w(Sebastien Hugo Darren Paul Daniel)
defp name_gen, do: elements(@usernames)
defp score_gen, do: nat
defp word_gen, do: binary(20)
defp index_gen, do: nat
defp submission_gen do
let [name, score, word] <- [name_gen, score_gen, word_gen] do
%Submission{username: name, word: word, score: score}
end
end
property "leaderboard is rock solid" do
forall cmds <- commands(__MODULE__) do
trap_exit do
Leaderboard.start_link(:sut)
{history, state, result} = run_commands(__MODULE__, cmds)
Leaderboard.stop(:sut)
(result == :ok)
|> when_fail(
IO.puts """
History: #{inspect history, pretty: true}
State: #{inspect state, pretty: true}
Result: #{inspect result, pretty: true}
""")
|> aggregate(command_names cmds)
end
end
end
#
# COMMANDS, PRE/POST-CONDITIONS
#
defstruct submissions: []
def initial_state, do: %__MODULE__{}
def command(_state) do
frequency([{1, {:call, Leaderboard, :at, [:sut, index_gen]}},
{1, {:call, Leaderboard, :record, [:sut, submission_gen]}},
{1, {:call, Leaderboard, :capacity, []}}
])
end
def precondition(state, {:call, Leaderboard, :at, [:sut, index]}) do
Enum.count(state.submissions) > index
end
def precondition(_state, _call) do
true
end
def next_state(state_before, _result, {:call, Leaderboard, :record, [:sut, a_submission]}) do
%__MODULE__{state_before | submissions: do_insert(state_before.submissions, a_submission)}
end
def next_state(state, _result, _call) do
state
end
defp do_insert([], a_submission) do
[a_submission]
end
defp do_insert([head | tail] = submissions, a_submission) do
cond do
head.score < a_submission.score -> [a_submission | submissions]
head.score >= a_submission.score -> [head | do_insert(tail, a_submission)]
end
end
def postcondition(state, {:call, Leaderboard, :at, [:sut, index]}, result) do
case index < Enum.count(state.submissions) do
true -> result != nil
false -> result == nil
end
end
def postcondition(_state, {:call, Leaderboard, :record, [:sut, _a_submission]}, result) do
result == :ok
end
def postcondition(_state, {:call, Leaderboard, :capacity, []}, result) do
result == Application.get_env(:myproject, :leaderboard_capacity)
end
end
The output:
...............
1) property leaderboard is rock solid (MyProject.LeaderboardProperties)
test/my_project/leaderboard_property.exs:24
Property leaderboard is rock solid failed. Counter-Example is:
[[]]
code: nil
stacktrace:
test/myproject/leaderboard_property.exs:24: (test)
Finished in 0.3 seconds
1 property, 15 tests, 1 failure
Randomized with seed 340578
Thanks