diff --git a/quickcheck/aeb_fate_encoding_tests.erl b/quickcheck/aeb_fate_encoding_tests.erl index 1bd8e1e..63993cc 100644 --- a/quickcheck/aeb_fate_encoding_tests.erl +++ b/quickcheck/aeb_fate_encoding_tests.erl @@ -22,5 +22,6 @@ quickcheck_test_() -> {setup, fun() -> eqc:start() end, [ ?EQC_EUNIT(aefate_type_eqc, prop_roundtrip, 1000), ?EQC_EUNIT(aefate_eqc, prop_serializes, 1000), + ?EQC_EUNIT(aefate_eqc, prop_no_maps_in_keys, 1000), ?EQC_EUNIT(aefate_eqc, prop_idempotent, 1000) ]}. diff --git a/quickcheck/aefate_code_eqc.erl b/quickcheck/aefate_code_eqc.erl index 72726a4..e412dfd 100644 --- a/quickcheck/aefate_code_eqc.erl +++ b/quickcheck/aefate_code_eqc.erl @@ -77,7 +77,7 @@ prop_opcodes() -> valid_opcodes() -> - lists:seq(0, 16#7c) ++ lists:seq(16#fa, 16#fd). + lists:seq(0, 16#7f) ++ lists:seq(16#fa, 16#fd). fate_code(Failure) -> @@ -85,7 +85,8 @@ fate_code(Failure) -> ?LET({FMap, SMap, AMap}, {non_empty(map(if Failure == 1 -> binary(1); true -> binary(4) end, - {{list(aefate_type_eqc:fate_type(Size div 3)), aefate_type_eqc:fate_type(Size div 3)}, bbs_code(Failure)})), + {sublist(lists:sort([private, payable])), %% deserialize sorts them + {list(aefate_type_eqc:fate_type(Size div 3)), aefate_type_eqc:fate_type(Size div 3)}, bbs_code(Failure)})), small_map(small_fate_data_key(5), small_fate_data(4)), small_map(small_fate_data_key(5), small_fate_data(4))}, aeb_fate_code:update_annotations( diff --git a/quickcheck/aefate_eqc.erl b/quickcheck/aefate_eqc.erl index 6b6db82..25823a0 100644 --- a/quickcheck/aefate_eqc.erl +++ b/quickcheck/aefate_eqc.erl @@ -10,6 +10,7 @@ -module(aefate_eqc). -include_lib("eqc/include/eqc.hrl"). +-include("../include/aeb_fate_data.hrl"). -compile([export_all, nowarn_export_all]). @@ -23,7 +24,7 @@ prop_roundtrip() -> end)). prop_format_scan() -> - ?FORALL(FateData, fate_data(), + ?FORALL(FateData, fate_data([variant, map]), ?WHENFAIL(eqc:format("Trying to format ~p failed~n", [FateData]), begin String = aeb_fate_data:format(FateData), @@ -43,6 +44,18 @@ prop_serializes() -> {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 aeb_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())), aeb_fate_encoding:serialize(FateData)), @@ -59,13 +72,14 @@ prop_fuzz() -> prop_order() -> - ?FORALL(Items, vector(3, fate_data()), + ?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, aeb_fate_data:lt(I, Min)])}, - {maximum, is_empty([ {Max, '<', I} || I<-Items, aeb_fate_data:lt(Max, I)])}]) + {maximum, is_empty([ {Max, '<', I} || I<-Items, aeb_fate_data:lt(Max, I)])}, + {asym, aeb_fate_data:lt(Min, Max) orelse Min == Max}]) end). lt_min([X, Y | Rest]) -> @@ -88,18 +102,24 @@ prop_idempotent() -> aeb_fate_encoding:sort(aeb_fate_encoding:sort(Items)))). + +fate_data(Kind) -> + ?SIZED(Size, ?LET(Data, fate_data(Size, Kind), eqc_symbolic:eval(Data))). + fate_data() -> - ?SIZED(Size, ?LET(Data, fate_data(Size, [map, variant]), eqc_symbolic:eval(Data))). + fate_data([map, variant, store_map]). +%% keys may contain variants but no maps fate_data_key() -> - ?SIZED(Size, ?LET(Data, fate_data(Size div 4, [variant]), eqc_symbolic:eval(Data))). + fate_data([variant]). -fate_data(0, _Options) -> +fate_data(0, Options) -> ?LAZY( frequency( - [{5, oneof([fate_integer(), fate_boolean(), fate_nil(), fate_unit()])}, - {1, oneof([fate_string(), fate_address(), fate_bytes(), fate_contract(), - fate_oracle(), fate_oracle_q(), fate_bits(), fate_channel()])}])); + [{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), @@ -148,9 +168,20 @@ fate_list(Size, Options) -> 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])), + ?LET(Keys, vector(length(Values), fate_data(Size div max(1, N * 2), Options -- [map, store_map])), return(aeb_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(aeb_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(aeb_fate_data:make_map(maps:from_list(lists:zip(Keys, Values))))))). + non_quote_string() -> ?SUCHTHAT(S, utf8(), [ quote || <<34>> <= S ] == []). @@ -167,3 +198,14 @@ injection(Binary) -> 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. diff --git a/src/aeb_fate_encoding.erl b/src/aeb_fate_encoding.erl index 3706322..1dcf8c8 100644 --- a/src/aeb_fate_encoding.erl +++ b/src/aeb_fate_encoding.erl @@ -487,5 +487,11 @@ sort(KVList) -> valid_key_type(K) when ?IS_FATE_MAP(K) -> error({map_as_key_in_map, K}); +valid_key_type(?FATE_STORE_MAP(_, _) = K) -> + error({map_as_key_in_map, K}); +valid_key_type(K) when is_list(K) -> + lists:all(fun(E) -> valid_key_type(E) end, K); +valid_key_type(K) when is_tuple(K) -> + lists:all(fun(E) -> valid_key_type(E) end, tuple_to_list(K)); valid_key_type(_K) -> true.