diff --git a/.gitignore b/.gitignore index e3e5e72..c6154b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ .eunit +.rebar .jiffy.dev *.app *.beam +*.d *.o *.so +_build +compile_commands.json deps erln8.config hexer.config +rebar.lock TEST-*.xml diff --git a/.travis.yml b/.travis.yml index fa26d15..4762c65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,17 +3,15 @@ notifications: email: paul.joseph.davis@gmail.com script: make check otp_release: + - 19.1 + - 19.0 + - 18.3 + - 18.2 - 17.5 - 17.4 - - 17.3 - - 17.1 - - 17.0 - R16B03-1 - R16B02 - - R16B01 - R15B03 - R15B02 - - R15B01 - R14B04 - R14B03 - - R14B02 diff --git a/Makefile b/Makefile index 025486e..f781311 100644 --- a/Makefile +++ b/Makefile @@ -15,17 +15,7 @@ distclean: clean git clean -fxd -devmarker: - @touch .jiffy.dev - - -depends: devmarker - @if test ! -d ./deps/proper; then \ - $(REBAR) get-deps; \ - fi - - -build: depends +build: $(REBAR) compile diff --git a/enc b/enc new file mode 100755 index 0000000..239a351 Binary files /dev/null and b/enc differ diff --git a/rebar.config b/rebar.config index 9d26448..33b8186 100644 --- a/rebar.config +++ b/rebar.config @@ -33,13 +33,8 @@ ]}. {eunit_opts, [ - verbose, - {report, { - eunit_surefire, [{dir,"."}] - }} -]}. - -{plugins, [ - rebar_gdb_plugin + verbose ]}. +{pre_hooks, [{"", compile, "./enc compile"}]}. +{post_hooks, [{"", clean, "./enc clean"}]}. diff --git a/rebar.config.script b/rebar.config.script index 8a0049d..42bc6ef 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -1,37 +1,18 @@ % This file is part of Jiffy released under the MIT license. % See the LICENSE file for more information. - -% Only include PropEr as a dependency when the JIFFY_DEV -% environment variable is defined. This allows downstream -% applications to avoid requiring PropEr. % -% This script is based on the example provided with Rebar. - -ErlOpts = [{d, 'JIFFY_DEV'}], +% Only run the EQC checks when EQC is present. -Proper = [ - {proper, ".*", {git, "git://github.com/manopapad/proper.git", "master"}} -], +HaveEQC = code:which(eqc) =/= non_existing, -ConfigPath = filename:dirname(SCRIPT), -DevMarker = filename:join([ConfigPath, ".jiffy.dev"]), +ErlOpts = if not HaveEQC -> []; true -> + [{d, 'HAVE_EQC'}] +end, -case filelib:is_file(DevMarker) of - true -> - % Don't override existing dependencies - Config0 = case lists:keyfind(deps, 1, CONFIG) of - false -> - CONFIG ++ [{deps, Proper}]; - {deps, DepsList} -> - lists:keyreplace(deps, 1, CONFIG, {deps, DepsList ++ Proper}) - end, - Config1 = case lists:keyfind(erl_opts, 1, Config0) of - false -> - Config0 ++ [{erl_opts, ErlOpts}]; - {erl_opts, Opts} -> - NewOpts = {erl_opts, Opts ++ ErlOpts}, - lists:keyreplace(erl_opts, 1, Config0, NewOpts) - end; +case lists:keyfind(erl_opts, 1, CONFIG) of + {erl_opts, Opts} -> + NewOpts = {erl_opts, Opts ++ ErlOpts}, + lists:keyreplace(erl_opts, 1, CONFIG, NewOpts); false -> - CONFIG + CONFIG ++ [{erl_opts, ErlOpts}] end. diff --git a/test/jiffy_01_yajl_tests.erl b/test/jiffy_01_yajl_tests.erl index aedaf71..689c880 100644 --- a/test/jiffy_01_yajl_tests.erl +++ b/test/jiffy_01_yajl_tests.erl @@ -5,6 +5,7 @@ -include_lib("eunit/include/eunit.hrl"). +-include("jiffy_util.hrl"). yajl_test_() -> @@ -19,7 +20,7 @@ gen({Name, Json, Erl}) -> read_cases() -> - CasesPath = filename:join(["..", "test", "cases", "*.json"]), + CasesPath = cases_path("*.json"), FileNames = lists:sort(filelib:wildcard(CasesPath)), lists:map(fun(F) -> make_pair(F) end, FileNames). diff --git a/test/jiffy_10_short_double_tests.erl b/test/jiffy_10_short_double_tests.erl index d66e1ea..0b099c1 100644 --- a/test/jiffy_10_short_double_tests.erl +++ b/test/jiffy_10_short_double_tests.erl @@ -8,7 +8,8 @@ -include("jiffy_util.hrl"). -filename() -> "../test/cases/short-doubles.txt". +filename() -> + cases_path("short-doubles.txt"). short_double_test_() -> diff --git a/test/jiffy_11_proper_tests.erl b/test/jiffy_11_proper_tests.erl deleted file mode 100644 index 23aa38d..0000000 --- a/test/jiffy_11_proper_tests.erl +++ /dev/null @@ -1,185 +0,0 @@ -% This file is part of Jiffy released under the MIT license. -% See the LICENSE file for more information. - --module(jiffy_11_proper_tests). - --ifdef(JIFFY_DEV). - --include_lib("proper/include/proper.hrl"). --include_lib("eunit/include/eunit.hrl"). --include("jiffy_util.hrl"). - -opts() -> - [ - {max_size, 15}, - {numtests, 1000} - ]. - -run(Name) -> - {msg("~s", [Name]), [ - {timeout, 300, ?_assert(proper:quickcheck(?MODULE:Name(), opts()))} - ]}. - -proper_encode_decode_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) - ]. - -prop_enc_dec() -> - ?FORALL(Data, json(), - begin - %io:format(standard_error, "Data: ~p~n", [Data]), - Data == jiffy:decode(jiffy:encode(Data)) - end - ). - -prop_dec_trailer() -> - ?FORALL({T1, T2}, {json(), json()}, - begin - B1 = jiffy:encode(T1), - B2 = jiffy:encode(T2), - Combiners = [ - <<" ">>, - <<"\r\t">>, - <<"\n \t">>, - <<" ">> - ], - lists:foreach(fun(Comb) -> - Bin = <>, - {has_trailer, T1, Rest} = jiffy:decode(Bin, [return_trailer]), - T2 = jiffy:decode(Rest) - end, Combiners), - true - end - ). - --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. - -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_dec_pretty() -> - ?FORALL(Data, json(), - begin - Data == jiffy:decode(jiffy:encode(Data, [pretty])) - end - ). - -prop_enc_no_crash() -> - ?FORALL(Data, any(), begin catch jiffy:encode(Data), true end). - -prop_dec_no_crash_bin() -> - ?FORALL(Data, binary(), begin catch jiffy:decode(Data), true end). - -prop_dec_no_crash_any() -> - ?FORALL(Data, any(), begin catch jiffy:decode(Data), true end). - - -% JSON Generation - - -json_null() -> - null. - - -json_boolean() -> - oneof([true, false]). - - -json_number() -> - oneof([integer(), float()]). - - -json_string() -> - escaped_utf8_bin(). - - -json_list(S) when S =< 0 -> - []; -json_list(S) -> - ?LETSHRINK( - [ListSize], - [integer(0, S)], - vector(ListSize, json_text(S - ListSize)) - ). - - -json_object(S) when S =< 0 -> - {[]}; -json_object(S) -> - ?LETSHRINK( - [ObjectSize], - [integer(0, S)], - {vector(ObjectSize, {json_string(), json_text(S - ObjectSize)})} - ). - - -json_value() -> - oneof([ - json_null(), - json_boolean(), - json_string(), - json_number() - ]). - - -json_text(S) when S > 0 -> - ?LAZY(oneof([ - json_list(S), - json_object(S) - ])); -json_text(_) -> - json_value(). - - -json() -> - ?SIZED(S, json_text(S)). - - -%% 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. - - -escaped_utf8_bin() -> - ?SUCHTHAT(Bin, - ?LET(S, ?SUCHTHAT(L, list(escaped_char()), L /= []), - unicode:characters_to_binary(S, unicode, utf8)), - is_binary(Bin) - ). - - -escaped_char() -> - ?LET(C, char(), - case C of - $" -> "\\\""; - C when C == 65534 -> 65533; - C when C == 65535 -> 65533; - C when C > 1114111 -> 1114111; - C -> C - end - ). - --endif. diff --git a/test/jiffy_11_property_tests.erl b/test/jiffy_11_property_tests.erl new file mode 100644 index 0000000..70d7333 --- /dev/null +++ b/test/jiffy_11_property_tests.erl @@ -0,0 +1,257 @@ +% 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. diff --git a/test/jiffy_util.hrl b/test/jiffy_util.hrl index 51bd86a..4b714d9 100644 --- a/test/jiffy_util.hrl +++ b/test/jiffy_util.hrl @@ -25,3 +25,16 @@ enc(V) -> enc(V, Opts) -> iolist_to_binary(jiffy:encode(V, Opts)). + + +%% rebar runs eunit with PWD as .eunit/ +%% rebar3 runs eunit with PWD as ./ +%% this adapts to the differences +cases_path(Suffix) -> + {ok, Cwd} = file:get_cwd(), + Prefix = case filename:basename(Cwd) of + ".eunit" -> ".."; + _ -> "." + end, + Path = "test/cases", + filename:join([Prefix, Path, Suffix]).