| % 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). |
| |
| |
| -compile(export_all). |
| |
| |
| -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 = iolist_to_binary(jiffy:encode(T1)), |
| B2 = iolist_to_binary(jiffy:encode(T2)), |
| Bin = <<B1/binary, Comb/binary, B2/binary>>, |
| {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. |