All checks were successful
Gajumaru Bytecode Tests / tests (push) Successful in -3m34s
Add Gitea tests Rename Remove oracle references Package for zx Reviewed-on: #235 Reviewed-by: dimitar.p.ivanov <dimitarivanov@qpq.swiss> Co-authored-by: Craig Everett <zxq9@zxq9.com> Co-committed-by: Craig Everett <zxq9@zxq9.com>
212 lines
8.5 KiB
Erlang
212 lines
8.5 KiB
Erlang
%%% @author Thomas Arts
|
|
%%% @doc Use `rebar3 as eqc shell` to run properties in the shell
|
|
%%%
|
|
%%% We need to be able to generate data that serializes with ?LONG_LIST, ?LONG_TUPLE etc.
|
|
%%% In other words make some rather broad terms as well as some deep terms
|
|
%%%
|
|
%%% @end
|
|
%%% Created : 13 Dec 2018 by Thomas Arts <thomas@SpaceGrey.lan>
|
|
|
|
-module(gmfate_eqc).
|
|
|
|
-include_lib("eqc/include/eqc.hrl").
|
|
-include("../include/gmb_fate_data.hrl").
|
|
|
|
-compile([export_all, nowarn_export_all]).
|
|
|
|
prop_roundtrip() ->
|
|
?FORALL(FateData, fate_data(),
|
|
measure(bytes, size(term_to_binary(FateData)),
|
|
begin
|
|
Serialized = gmb_fate_encoding:serialize(FateData),
|
|
?WHENFAIL(eqc:format("Serialized ~p to ~p~n", [FateData, Serialized]),
|
|
equals(gmb_fate_encoding:deserialize(Serialized), FateData))
|
|
end)).
|
|
|
|
prop_format_scan() ->
|
|
?FORALL(FateData, fate_data([variant, map]),
|
|
?WHENFAIL(eqc:format("Trying to format ~p failed~n", [FateData]),
|
|
begin
|
|
String = gmb_fate_data:format(FateData),
|
|
{ok, _Scanned, _} = gmb_fate_asm_scan:scan(unicode:characters_to_list(String)),
|
|
true
|
|
end)).
|
|
|
|
prop_serializes() ->
|
|
?FORALL({Data, Garbage}, {fate_data(), binary()},
|
|
?WHENFAIL(eqc:format("Trying to serialize/deserialize ~p failed~n", [Data]),
|
|
begin
|
|
Binary = <<(gmb_fate_encoding:serialize(Data))/binary, Garbage/binary>>,
|
|
{FateData, Rest} = gmb_fate_encoding:deserialize_one(Binary),
|
|
measure(binary_size, size(Binary),
|
|
conjunction([{equal, equals(Data, FateData)},
|
|
{rest, equals(Garbage, Rest)},
|
|
{size, size(Binary) < 500000}]))
|
|
end)).
|
|
|
|
prop_no_maps_in_keys() ->
|
|
?FORALL(FateData, fate_bad_map(), %% may contain a map in its keys
|
|
begin
|
|
HasMapInKeys = lists:any(fun(K) -> has_map(K) end, maps:keys(FateData)),
|
|
try gmb_fate_encoding:serialize(FateData),
|
|
?WHENFAIL(eqc:format("Should not serialize, contains a map in key\n", []),
|
|
not HasMapInKeys)
|
|
catch error:Reason ->
|
|
?WHENFAIL(eqc:format("(~p) Should serialize\n", [Reason]), HasMapInKeys)
|
|
end
|
|
end).
|
|
|
|
prop_fuzz() ->
|
|
in_parallel(
|
|
?FORALL(Binary, ?LET(FateData, ?SIZED(Size, resize(Size div 4, fate_data())), gmb_fate_encoding:serialize(FateData)),
|
|
?FORALL(InjectedBin, injection(Binary),
|
|
try Org = gmb_fate_encoding:deserialize(InjectedBin),
|
|
NewBin = gmb_fate_encoding:serialize(Org),
|
|
NewOrg = gmb_fate_encoding:deserialize(NewBin),
|
|
measure(success, 1,
|
|
?WHENFAIL(eqc:format("Deserialize ~p gives\n~p\nSerializes to ~p\n", [InjectedBin, Org, NewOrg]),
|
|
equals(NewBin, InjectedBin)))
|
|
catch _:_ ->
|
|
true
|
|
end))).
|
|
|
|
|
|
prop_order() ->
|
|
?FORALL(Items, vector(3, fate_data([variant, map])),
|
|
begin
|
|
%% Use lt to take minimum
|
|
Min = lt_min(Items),
|
|
Max = lt_max(Items),
|
|
conjunction([ {minimum, is_empty([ {Min, '>', I} || I<-Items, gmb_fate_data:lt(I, Min)])},
|
|
{maximum, is_empty([ {Max, '<', I} || I<-Items, gmb_fate_data:lt(Max, I)])},
|
|
{asym, gmb_fate_data:lt(Min, Max) orelse Min == Max}])
|
|
end).
|
|
|
|
lt_min([X, Y | Rest]) ->
|
|
case gmb_fate_data:lt(X, Y) of
|
|
true -> lt_min([X | Rest]);
|
|
false -> lt_min([Y| Rest])
|
|
end;
|
|
lt_min([X]) -> X.
|
|
|
|
lt_max([X, Y | Rest]) ->
|
|
case gmb_fate_data:lt(X, Y) of
|
|
true -> lt_max([Y | Rest]);
|
|
false -> lt_max([X| Rest])
|
|
end;
|
|
lt_max([X]) -> X.
|
|
|
|
prop_idempotent() ->
|
|
?FORALL(Items, list({fate_data_key(), fate_data()}),
|
|
equals(gmb_fate_encoding:sort(Items),
|
|
gmb_fate_encoding:sort(gmb_fate_encoding:sort(Items)))).
|
|
|
|
|
|
|
|
fate_data(Kind) ->
|
|
?SIZED(Size, ?LET(Data, fate_data(Size, Kind), eqc_symbolic:eval(Data))).
|
|
|
|
fate_data() ->
|
|
fate_data([map, variant, store_map]).
|
|
|
|
%% keys may contain variants but no maps
|
|
fate_data_key() ->
|
|
fate_data([variant]).
|
|
|
|
fate_data(0, Options) ->
|
|
?LAZY(
|
|
frequency(
|
|
[{50, oneof([fate_integer(), fate_boolean(), fate_nil(), fate_unit()])},
|
|
{10, oneof([fate_string(), fate_address(), fate_bytes(), fate_contract(),
|
|
fate_oracle(), fate_oracle_q(), fate_bits(), fate_channel()])}] ++
|
|
[{1, fate_store_map()} || lists:member(store_map, Options)]));
|
|
fate_data(Size, Options) ->
|
|
?LAZY(
|
|
oneof([fate_data(0, Options),
|
|
fate_list(Size, Options),
|
|
fate_tuple(Size, Options)] ++
|
|
[fate_variant(Size, Options)
|
|
|| lists:member(variant, Options)] ++
|
|
[fate_map(Size, Options)
|
|
|| lists:member(map, Options)])).
|
|
|
|
|
|
fate_integer() -> ?LET(X, oneof([int(), largeint()]), return(gmb_fate_data:make_integer(X))).
|
|
fate_bits() -> ?LET(X, oneof([int(), largeint()]), return(gmb_fate_data:make_bits(X))).
|
|
fate_boolean() -> ?LET(X, elements([true, false]), return(gmb_fate_data:make_boolean(X))).
|
|
fate_nil() -> gmb_fate_data:make_list([]).
|
|
fate_unit() -> gmb_fate_data:make_unit().
|
|
fate_string() -> ?LET(X, frequency([{10, non_quote_string()}, {2, list(non_quote_string())},
|
|
{1, ?LET(N, choose(64-3, 64+3), vector(N, $a))}]),
|
|
return(gmb_fate_data:make_string(X))).
|
|
fate_address() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_address(X))).
|
|
fate_bytes() -> ?LET(X, non_empty(binary()), return(gmb_fate_data:make_bytes(X))).
|
|
fate_contract() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_contract(X))).
|
|
fate_oracle() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_oracle(X))).
|
|
fate_oracle_q() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_oracle_query(X))).
|
|
fate_channel() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_channel(X))).
|
|
|
|
fate_values(Size, N, Options) ->
|
|
eqc_gen:list(N, fate_data(Size div max(1, N), Options)).
|
|
|
|
%% May shrink to fate_unit
|
|
fate_tuple(Size, Options) ->
|
|
?LET(N, choose(0, 6),
|
|
?LETSHRINK(Elements, fate_values(Size, N, Options),
|
|
return(gmb_fate_data:make_tuple(list_to_tuple(Elements))))).
|
|
|
|
fate_variant(Size, Options) ->
|
|
?LET({L1, L2, {tuple, Args}}, {list(choose(0, 255)), list(choose(0,255)), fate_tuple(Size, Options)},
|
|
return(gmb_fate_data:make_variant(L1 ++ [tuple_size(Args)] ++ L2,
|
|
length(L1), Args))).
|
|
|
|
fate_list(Size, Options) ->
|
|
?LET(N, frequency([{20, choose(0, 6)}, {1, choose(64 - 3, 64 + 3)}]),
|
|
?LETSHRINK(Vs, fate_values(Size, N, Options),
|
|
return(gmb_fate_data:make_list(Vs)))).
|
|
|
|
fate_map(Size, Options) ->
|
|
?LET(N, choose(0, 6),
|
|
?LETSHRINK(Values, fate_values(Size, N, Options),
|
|
?LET(Keys, vector(length(Values), fate_data(Size div max(1, N * 2), Options -- [map, store_map])),
|
|
return(gmb_fate_data:make_map(maps:from_list(lists:zip(Keys, Values))))))).
|
|
|
|
fate_store_map() ->
|
|
%% only #{} is allowed as cache in serialization
|
|
?LET(X, oneof([int(), largeint()]),
|
|
return(gmb_fate_data:make_store_map(abs(X)))).
|
|
|
|
fate_bad_map() ->
|
|
?LET(N, choose(0, 6),
|
|
?LET(Values, vector(N, ?SIZED(Size, resize(Size div 8, fate_data()))),
|
|
?LET(Keys, vector(N, ?SIZED(Size, resize(Size div 4, fate_data()))),
|
|
return(gmb_fate_data:make_map(maps:from_list(lists:zip(Keys, Values))))))).
|
|
|
|
non_quote_string() ->
|
|
?SUCHTHAT(S, utf8(), [ quote || <<34>> <= S ] == []).
|
|
|
|
char() ->
|
|
choose(1, 255).
|
|
|
|
injection(Binary) ->
|
|
?LET({N, Inj}, {choose(0, byte_size(Binary) - 1), choose(0,255)},
|
|
begin
|
|
M = N * 8,
|
|
<<X:M, _:8, Z/binary>> = Binary,
|
|
<<X:M, Inj:8, Z/binary>>
|
|
end).
|
|
|
|
is_empty(L) ->
|
|
?WHENFAIL(eqc:format("~p\n", [L]), L == []).
|
|
|
|
has_map(L) when is_list(L) ->
|
|
lists:any(fun(V) -> has_map(V) end, L);
|
|
has_map(T) when is_tuple(T) ->
|
|
has_map(tuple_to_list(T));
|
|
has_map(M) when is_map(M) ->
|
|
true;
|
|
has_map(?FATE_STORE_MAP(_, _)) ->
|
|
true;
|
|
has_map(_) ->
|
|
false.
|