From d69375e72ba79e465a340fdf91767bfde75ae7b7 Mon Sep 17 00:00:00 2001 From: Thomas Arts Date: Thu, 2 May 2019 13:07:14 +0200 Subject: [PATCH 1/2] Fix types --- src/aeb_fate_data.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/aeb_fate_data.erl b/src/aeb_fate_data.erl index 04f78e2..4adfb71 100644 --- a/src/aeb_fate_data.erl +++ b/src/aeb_fate_data.erl @@ -24,9 +24,9 @@ -type fate_type_type() :: integer | boolean - | {list, fate_type()} - | {map, fate_type(), fate_type()} - | {tuple, [fate_type()]} + | {list, fate_type_type()} + | {map, fate_type_type(), fate_type_type()} + | {tuple, [fate_type_type()]} | address | hash | signature @@ -35,7 +35,8 @@ | name | channel | bits - | {variant, list()}. + | string + | {variant, [fate_type_type()]}. -type fate_type() :: -- 2.30.2 From 163e805f55d0bf9068aabb0f5dbb687f138622ad Mon Sep 17 00:00:00 2001 From: Thomas Arts Date: Thu, 11 Apr 2019 13:39:42 +0200 Subject: [PATCH 2/2] Enable running properties as Eunit tests Update model to address serialization Update eunit test wrapper Update tests Add tests for serialize_type --- quickcheck/aeb_fate_data_tests.erl | 25 ++++++++++ quickcheck/aeb_fate_encoding_tests.erl | 25 ++++++++++ quickcheck/aefate_eqc.erl | 68 ++++++++++++++++---------- quickcheck/aefate_type_eqc.erl | 49 +++++++++++++++++++ 4 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 quickcheck/aeb_fate_data_tests.erl create mode 100644 quickcheck/aeb_fate_encoding_tests.erl create mode 100644 quickcheck/aefate_type_eqc.erl diff --git a/quickcheck/aeb_fate_data_tests.erl b/quickcheck/aeb_fate_data_tests.erl new file mode 100644 index 0000000..81c911d --- /dev/null +++ b/quickcheck/aeb_fate_data_tests.erl @@ -0,0 +1,25 @@ +%%% @author Thomas Arts +%%% @doc Allow to run QuickCheck tests as eunit tests +%%% `rebar3 as eqc eunit --cover` +%%% or `rebar3 as eqc eunit --module=aeb_fate_data` +%%% Note that for obtainign cover file, one needs `rebar3 as eqc cover +%%% +%%% +%%% @end +%%% Created : 13 Dec 2018 by Thomas Arts + +-module(aeb_fate_data_tests). + +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +-define(EQC_EUNIT(Module, PropName, Ms), + { atom_to_list(PropName), + {timeout, (Ms * 3) / 1000, ?_assert(eqc:quickcheck(eqc:testing_time(Ms / 1000, Module:PropName())))}}). + +quickcheck_test_() -> + {setup, fun() -> eqc:start() end, + [ ?EQC_EUNIT(aefate_eqc, prop_roundtrip, 500), + ?EQC_EUNIT(aefate_eqc, prop_format_scan, 2000) + ]}. diff --git a/quickcheck/aeb_fate_encoding_tests.erl b/quickcheck/aeb_fate_encoding_tests.erl new file mode 100644 index 0000000..6bc94d2 --- /dev/null +++ b/quickcheck/aeb_fate_encoding_tests.erl @@ -0,0 +1,25 @@ +%%% @author Thomas Arts +%%% @doc Allow to run QuickCheck tests as eunit tests +%%% `rebar3 as eqc eunit --cover` +%%% or `rebar3 as eqc eunit --module=aeb_fate_encoding` +%%% Note that for obtaining cover file, one needs `rebar3 as eqc cover +%%% +%%% +%%% @end +%%% Created : 13 Dec 2018 by Thomas Arts + +-module(aeb_fate_encoding_tests). + +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +-define(EQC_EUNIT(Module, PropName, Ms), + { atom_to_list(PropName), + {timeout, (Ms * 3) / 1000, ?_assert(eqc:quickcheck(eqc:testing_time(Ms / 1000, Module:PropName())))}}). + +quickcheck_test_() -> + {setup, fun() -> eqc:start() end, + [ ?EQC_EUNIT(aefate_type_eqc, prop_roundtrip, 1000), + ?EQC_EUNIT(aefate_eqc, prop_serializes, 1000) + ]}. diff --git a/quickcheck/aefate_eqc.erl b/quickcheck/aefate_eqc.erl index 7be968e..0db5677 100644 --- a/quickcheck/aefate_eqc.erl +++ b/quickcheck/aefate_eqc.erl @@ -1,7 +1,8 @@ %%% @author Thomas Arts -%%% @copyright (C) 2018, 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 @@ -21,14 +22,6 @@ prop_roundtrip() -> equals(aeb_fate_encoding:deserialize(Serialized), FateData)) end)). -prop_format() -> - ?FORALL(FateData, fate_data(), - ?WHENFAIL(eqc:format("Trying to format ~p failed~n",[FateData]), - begin - String = aeb_fate_data:format(FateData), - collect([FateData, unicode:characters_to_binary(String, latin1)], true) - end)). - prop_format_scan() -> ?FORALL(FateData, fate_data(), ?WHENFAIL(eqc:format("Trying to format ~p failed~n", [FateData]), @@ -38,10 +31,28 @@ prop_format_scan() -> true end)). -fate_data() -> - ?SIZED(Size, ?LET(Data, fate_type(Size, [map]), eqc_symbolic:eval(Data))). +prop_serializes() -> + ?FORALL(FateDatas, non_empty(?SIZED(Size, resize(Size div 2, list(fate_data())))), + ?WHENFAIL(eqc:format("Trying to serialize/deserialize ~p failed~n", [FateDatas]), + begin + {T1, Binary} = + timer:tc( fun() -> + << begin B = aeb_fate_encoding:serialize(Data), + <> end || Data <- FateDatas >> + end), + {T2, {FateData, _}} = + timer:tc(fun() -> aeb_fate_encoding:deserialize_one(Binary) end), + measure(binary_size, size(Binary), + measure(encode, T1, + measure(decode, T2, + conjunction([{equal, equals(hd(FateDatas), FateData)}, + {size, size(Binary) < 500000}])))) + end)). -fate_type(0, _Options) -> +fate_data() -> + ?SIZED(Size, ?LET(Data, fate_data(Size, [map]), eqc_symbolic:eval(Data))). + +fate_data(0, _Options) -> ?LAZY( oneof([fate_integer(), fate_boolean(), @@ -56,17 +67,16 @@ fate_type(0, _Options) -> fate_name(), fate_bits(), fate_channel()])); -fate_type(Size, Options) -> - ?LETSHRINK([Smaller], [?LAZY(fate_type(Size div 5, Options))], - oneof([?LAZY(fate_type(Size - 1, Options)), - ?LAZY(fate_list( Smaller )), - ?LAZY(fate_tuple( list( Smaller ))), - ?LAZY(fate_variant(?LET(L, list( Smaller), list_to_tuple(L))))] ++ +fate_data(Size, Options) -> + oneof([?LAZY(fate_data(Size - 1, Options)), + ?LAZY(fate_list( fate_data(Size div 5, Options) )), + ?LAZY(fate_tuple( list(fate_data(Size div 5, Options)) )), + ?LAZY(fate_variant( list(fate_data(Size div 5, Options)))) ] ++ [ - ?LAZY(fate_map( fate_type(Size div 3, Options -- [map]), - Smaller)) + ?LAZY(fate_map( fate_data(Size div 8, Options -- [map]), + fate_data(Size div 5, Options))) || lists:member(map, Options) - ])). + ]). fate_integer() -> {call, aeb_fate_data, make_integer, [oneof([int(), largeint()])]}. @@ -74,7 +84,9 @@ fate_bits() -> {call, aeb_fate_data, make_bits, [oneof([int(), largeint()] fate_boolean() -> {call, aeb_fate_data, make_boolean, [elements([true, false])]}. fate_nil() -> {call, aeb_fate_data, make_list, [[]]}. fate_unit() -> {call, aeb_fate_data, make_unit, []}. -fate_string() -> {call, aeb_fate_data, make_string, [?SUCHTHAT(S, utf8(), [ quote || <<34>> <= S ] == [])]}. +fate_string() -> {call, aeb_fate_data, make_string, + [frequency([{10, non_quote_string()}, {2, list(non_quote_string())}, + {1, ?LET(N, choose(64-3, 64+3), vector(N, $a))}])]}. fate_address() -> {call, aeb_fate_data, make_address, [non_zero_binary(256 div 8)]}. fate_hash() -> {call, aeb_fate_data, make_hash, [non_zero_binary(32)]}. fate_signature() -> {call, aeb_fate_data, make_signature, [non_zero_binary(64)]}. @@ -87,12 +99,13 @@ fate_channel() -> {call, aeb_fate_data, make_channel, [non_zero_binary(256 di fate_tuple(ListGen) -> {call, aeb_fate_data, make_tuple, [?LET(Elements, ListGen, list_to_tuple(Elements))]}. -fate_variant(TupleGen) -> - ?LET({L1, L2, Tuple}, {list(choose(0, 255)), list(choose(0,255)), TupleGen}, - {call, aeb_fate_data, make_variant, [L1 ++ [size(Tuple)] ++ L2, length(L1), Tuple]}). +fate_variant(ListGen) -> + ?LET({L1, L2, TupleAsList}, {list(choose(0, 255)), list(choose(0,255)), ListGen}, + {call, aeb_fate_data, make_variant, + [L1 ++ [length(TupleAsList)] ++ L2, length(L1), list_to_tuple(TupleAsList)]}). fate_list(Gen) -> - {call, aeb_fate_data, make_list, [oneof([fate_nil(), ?SHRINK(list(Gen), [fate_nil()])])]}. + {call, aeb_fate_data, make_list, [frequency([{20, list(Gen)}, {1, ?LET(N, choose(64-3, 64+3), vector(N, Gen))}])]}. fate_map(KeyGen, ValGen) -> {call, aeb_fate_data, make_map, [map(KeyGen, ValGen)]}. @@ -102,5 +115,8 @@ non_zero_binary(N) -> Bits = N*8, ?SUCHTHAT(Bin, binary(N), begin <> = Bin, V =/= 0 end). +non_quote_string() -> + ?SUCHTHAT(S, utf8(), [ quote || <<34>> <= S ] == []). + char() -> choose(1, 255). diff --git a/quickcheck/aefate_type_eqc.erl b/quickcheck/aefate_type_eqc.erl new file mode 100644 index 0000000..7d5596b --- /dev/null +++ b/quickcheck/aefate_type_eqc.erl @@ -0,0 +1,49 @@ +%%% @author Thomas Arts +%%% @doc Use `rebar3 as eqc shell` to run properties in the shell +%%% Properties for testing Fate type representations +%%% +%%% @end +%%% Created : 13 Dec 2018 by Thomas Arts + +-module(aefate_type_eqc). + +-include_lib("eqc/include/eqc.hrl"). + +-compile([export_all, nowarn_export_all]). + +prop_roundtrip() -> + ?FORALL(FateType, fate_type(), + collect(FateType, + begin + Serialized = aeb_fate_encoding:serialize_type(FateType), + BinSerialized = list_to_binary(Serialized), + ?WHENFAIL(eqc:format("Serialized ~p to ~p (~p)~n", [FateType, Serialized, BinSerialized]), + begin + {Type, <<>>} = aeb_fate_encoding:deserialize_type(BinSerialized), + equals(Type, FateType) + end) + end)). + + +fate_type() -> + ?SIZED(Size, fate_type(Size)). + +fate_type(0) -> + oneof([integer, + boolean, + address, + hash, + signature, + contract, + oracle, + name, + channel, + bits, + string]); +fate_type(Size) -> + oneof([?LAZY(fate_type(Size div 2)), + {list, ?LAZY(fate_type(Size div 2))}, + {tuple, list(?LAZY(fate_type(Size div 2)))}, + {variant, list(?LAZY(fate_type(Size div 2)))}, + ?LETSHRINK([T1, T2], [?LAZY(fate_type(Size div 2)), ?LAZY(fate_type(Size div 2))], + {map, T1, T2})]). -- 2.30.2