% This file is part of Jiffy released under the MIT license. % See the LICENSE file for more information. -module(jiffy_11_property_tests). -ifdef(HAVE_EQC). -include_lib("eqc/include/eqc.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("jiffy_util.hrl"). property_test_() -> [ run(prop_enc_dec), run(prop_enc_dec_pretty), run(prop_dec_trailer), run(prop_enc_no_crash), run(prop_dec_no_crash_bin), run(prop_dec_no_crash_any) ] ++ map_props(). -ifndef(JIFFY_NO_MAPS). map_props() -> [ run(prop_map_enc_dec) ]. -else. map_props() -> []. -endif. prop_enc_dec() -> ?FORALL(Data, json(), begin Data == jiffy:decode(jiffy:encode(Data)) end). prop_dec_trailer() -> ?FORALL({T1, Comb, T2}, {json(), combiner(), json()}, begin B1 = jiffy:encode(T1), B2 = jiffy:encode(T2), Bin = <>, {has_trailer, T1, Rest} = jiffy:decode(Bin, [return_trailer]), T2 = jiffy:decode(Rest), true end ). prop_enc_dec_pretty() -> ?FORALL(Data, json(), begin Data == jiffy:decode(jiffy:encode(Data, [pretty])) end ). -ifndef(JIFFY_NO_MAPS). prop_map_enc_dec() -> ?FORALL(Data, json(), begin MapData = to_map_ejson(Data), MapData == jiffy:decode(jiffy:encode(MapData), [return_maps]) end ). -endif. prop_enc_no_crash() -> ?FORALL(Data, any(), begin catch jiffy:encode(Data), true end). prop_dec_no_crash_any() -> ?FORALL(Data, any(), begin catch jiffy:decode(Data), true end). prop_dec_no_crash_bin() -> ?FORALL(Data, binary(), begin catch jiffy:decode(Data), true end). opts() -> [ {numtests, [1000]} ]. apply_opts(Prop) -> apply_opts(Prop, opts()). apply_opts(Prop, []) -> Prop; apply_opts(Prop, [{Name, Args} | Rest]) -> NewProp = erlang:apply(eqc, Name, Args ++ [Prop]), apply_opts(NewProp, Rest). log(F, A) -> io:format(standard_error, F, A). run(Name) -> Prop = apply_opts(?MODULE:Name()), {msg("~s", [Name]), [ {timeout, 300, ?_assert(eqc:quickcheck(Prop))} ]}. -ifndef(JIFFY_NO_MAPS). to_map_ejson({Props}) -> NewProps = [{K, to_map_ejson(V)} || {K, V} <- Props], maps:from_list(NewProps); to_map_ejson(Vals) when is_list(Vals) -> [to_map_ejson(V) || V <- Vals]; to_map_ejson(Val) -> Val. -endif. % Random any term generation any() -> ?SIZED(Size, any(Size)). any(0) -> any_value(); any(S) -> oneof(any_value_types() ++ [ ?LAZY(any_list(S)), ?LAZY(any_tuple(S)) ]). any_value() -> oneof(any_value_types()). any_value_types() -> [ largeint(), int(), real(), atom(), binary() ]. any_list(0) -> []; any_list(Size) -> ListSize = Size div 5, vector(ListSize, any(Size div 2)). any_tuple(0) -> {}; any_tuple(Size) -> ?LET(L, any_list(Size), list_to_tuple(L)). % JSON Generation json() -> ?SIZED(Size, json(Size)). json(0) -> oneof([ json_null(), json_true(), json_false(), json_number(), json_string() ]); json(Size) -> frequency([ {1, json_null()}, {1, json_true()}, {1, json_false()}, {1, json_number()}, {1, json_string()}, {5, ?LAZY(json_array(Size))}, {5, ?LAZY(json_object(Size))} ]). json_null() -> null. json_true() -> true. json_false() -> false. json_number() -> oneof([largeint(), int(), real()]). json_string() -> utf8(). json_array(0) -> []; json_array(Size) -> ArrSize = Size div 5, vector(ArrSize, json(Size div 2)). json_object(0) -> {[]}; json_object(Size) -> ObjSize = Size div 5, {vector(ObjSize, {json_string(), json(Size div 2)})}. combiner() -> ?SIZED( Size, ?LET( L, vector((Size div 4) + 1, oneof([$\r, $\n, $\t, $\s])), list_to_binary(L) ) ). atom() -> ?LET(L, ?SIZED(Size, vector(Size rem 254, char())), list_to_atom(L)). %% XXX: Add generators % % We should add generators that generate JSON binaries directly % so we can test things that aren't produced by the encoder. % % We should also have a version of the JSON generator that inserts % errors into the JSON that we can test for. -endif.