-module(test_eqc).
-include_lib("pbt.hrl").
-compile([export_all]).
-spec initial_state() -> eqc_statem:symbol_state().
initial_state() ->
#state{
locals = #{},
script = []
}.
command_precondition_common(_S, _Ast) ->
true.
precondition_common(_State, _Call) ->
true.
expr_args(#state{} = S) ->
[S#state.script, spec:gen(S)].
expr_pre(#state{}) ->
true.
expr_pre(_S, [_Script, {emit, _}]) ->
false;
expr_pre(_S, [_Script, drop]) ->
false;
expr_pre(_S, [Script, Ast]) ->
spec:valid([Ast | Script]).
expr(Script, Ast) ->
remote_eval(tremor_script_eval, [Script, Ast]).
exprenqueue_post(#state{}, [Script, Ast], RustResult) ->
{_, ModelResult} = lists:foldr(fun(AAst, {S, _}) ->
model:eval(S, AAst)
end, {#vars{}, null}, [Ast | Script]),
case ModelResult =:= RustResult of
true -> true;
false ->
io:format("~ts", [asts_to_script([Ast | Script])]),
io:format("model: ~p =/= tremor: ~p\n", [ModelResult, RustResult]),
false
end.
expr_next(#state{} = S, _Eval, [Script, Ast]) ->
S#state{script = [Ast | Script]}.
let_local_int_args(#state{} = S) ->
[S#state.script, spec:id(), spec:gen_int(S)].
let_local_int_pre(#state{}) ->
true.
let_local_int_pre(_S, [Script, _, Ast]) ->
spec:valid([Ast | Script]).
let_local_int(Script, Id, Expr) ->
remote_eval(tremor_script_eval, [Script, {'let', {local, Id}, Expr}]).
let_local_int_post(#state{}, [Script, Id, Expr], RustResult) ->
Ast = {'let', {local, Id}, Expr},
{_, ModelResult} = lists:foldr(fun(AAst, {S, _}) ->
model:eval(S, AAst)
end, {#vars{}, null}, [Ast | Script]),
case ModelResult =:= RustResult of
true -> true;
false ->
io:format("\nAst:\n~p\n", [lists:reverse([Ast | Script])]),
io:format("Script:\n~ts\n", [asts_to_script([Ast | Script])]),
io:format("model: ~p =/= tremor: ~p\n", [ModelResult, RustResult]),
false
end.
let_local_int_next(#state{} = S, _Eval, [Script, Id, Expr]) ->
Ast = {'let', {local, Id}, Expr},
S#state{
script = [Ast | Script],
locals = maps:put(Id, int, S#state.locals)
}.
let_local_float_args(#state{} = S) ->
[S#state.script, spec:id(), spec:gen_float(S)].
let_local_float_pre(#state{}) ->
true.
let_local_float_pre(_S, [Script, _, Ast]) ->
spec:valid([Ast | Script]).
let_local_float(Script, Id, Expr) ->
remote_eval(tremor_script_eval, [Script, {'let', {local, Id}, Expr}]).
let_local_float_post(#state{}, [Script, Id, Expr], RustResult) ->
Ast = {'let', {local, Id}, Expr},
{_, ModelResult} = lists:foldr(fun(AAst, {S, _}) ->
model:eval(S, AAst)
end, {#vars{}, null}, [Ast | Script]),
case ModelResult =:= RustResult of
true -> true;
false ->
io:format("\nAst:\n~p\n", [lists:reverse([Ast | Script])]),
io:format("Script:\n~ts\n", [asts_to_script([Ast | Script])]),
io:format("model: ~p =/= tremor: ~p\n", [ModelResult, RustResult]),
false
end.
let_local_float_next(#state{} = S, _Eval, [Script, Id, Expr]) ->
Ast = {'let', {local, Id}, Expr},
S#state{
script = [Ast | Script],
locals = maps:put(Id, float, S#state.locals)
}.
let_local_bool_args(#state{} = S) ->
[S#state.script, spec:id(), spec:gen_bool(S)].
let_local_bool_pre(#state{}) ->
true.
let_local_bool_pre(_S, [Script, _, Ast]) ->
spec:valid([Ast | Script]).
let_local_bool(Script, Id, Expr) ->
remote_eval(tremor_script_eval, [Script, {'let', {local, Id}, Expr}]).
let_local_bool_post(#state{}, [Script, Id, Expr], RustResult) ->
Ast = {'let', {local, Id}, Expr},
{_, ModelResult} = lists:foldr(fun(AAst, {S, _}) ->
model:eval(S, AAst)
end, {#vars{}, null}, [Ast | Script]),
case ModelResult =:= RustResult of
true -> true;
false ->
io:format("\nAst:\n~p\n", [lists:reverse([Ast | Script])]),
io:format("Script:\n~ts\n", [asts_to_script([Ast | Script])]),
io:format("model: ~p =/= tremor: ~p\n", [ModelResult, RustResult]),
false
end.
let_local_bool_next(#state{} = S, _Eval, [Script, Id, Expr]) ->
Ast = {'let', {local, Id}, Expr},
S#state{
script = [Ast | Script],
locals = maps:put(Id, bool, S#state.locals)
}.
let_local_string_args(#state{} = S) ->
[S#state.script, spec:id(), spec:gen_string(S)].
let_local_string_pre(#state{}) ->
true.
let_local_string_pre(_S, [Script, _, Ast]) ->
spec:valid([Ast | Script]).
let_local_string(Script, Id, Expr) ->
remote_eval(tremor_script_eval, [Script, {'let', {local, Id}, Expr}]).
let_local_string_post(#state{}, [Script, Id, Expr], RustResult) ->
Ast = {'let', {local, Id}, Expr},
{_, ModelResult} = lists:foldr(fun(AAst, {S, _}) ->
model:eval(S, AAst)
end, {#vars{}, null}, [Ast | Script]),
case ModelResult =:= RustResult of
true -> true;
false ->
io:format("\nAst:\n~p\n", [lists:reverse([Ast | Script])]),
io:format("Script:\n~ts\n", [asts_to_script([Ast | Script])]),
io:format("model: ~p =/= tremor: ~p\n", [ModelResult, RustResult]),
false
end.
let_local_string_next(#state{} = S, _Eval, [Script, Id, Expr]) ->
Ast = {'let', {local, Id}, Expr},
S#state{
script = [Ast | Script],
locals = maps:put(Id, string, S#state.locals)
}.
asts_to_script(Script) ->
lists:foldr(fun(Ast, Result) ->
Line = gen_script:gen(Ast),
<<Result/binary, Line/binary, ";\n">>
end, <<>>, Script).
tremor_script_eval(Spec) ->
tremor_script_eval([], Spec).
tremor_script_eval(Init, Spec) ->
Testcase = asts_to_script([Spec | Init]),
RustResult = ts:eval(Testcase),
ErlangResult = jsx:decode(RustResult),
util:clamp(ErlangResult, 13).
-spec prop_simple_expr() -> eqc:property().
prop_simple_expr() ->
?FORALL(Params, ?SUCHTHAT(Ast, spec:gen(initial_state()), spec:valid([Ast])),
begin
RustResult = remote_eval(tremor_script_eval, [Params]),
{ _, ModelResult } = model:eval(Params),
?WHENFAIL(
io:format("SIMPLE EXPR MODEL FAILED!\n AST: ~p\n Script: ~s\n Expected Result: ~p\n Actual Result: ~p\n",
[Params, gen_script:gen(Params), ModelResult, RustResult]),
ModelResult =:= RustResult
)
end).
-spec prop_simple_expr_with_state() -> eqc:property().
prop_simple_expr_with_state() ->
?FORALL(Cmds, commands(?MODULE),
begin
{History, State, Result} = run_commands(Cmds, []),
Success =
case Result of
ok ->
true;
_ ->
io:format("[~p] Res1: ~p~n", [?LINE, Result]),
false
end,
pretty_commands(?MODULE, Cmds, {History, State, Result}, Success)
end
).
-ifdef(REMOTE_EVAL).
maybe_client() ->
case ct_slave:start(eqc_client) of
{ok, Client} ->
rpc:call(Client, code, add_paths, [code:get_path()]),
{ok, Client};
{error, already_started, Client} ->
{ok, Client};
E ->
io:format(user, ">>>> E: ~p~n", [E]),
E
end.
-else.
maybe_client() ->
{ok, node()}.
-endif.
remote_eval(Fn, Args) ->
{ok, Client} = maybe_client(),
rpc:call(Client, ?MODULE, Fn, Args).