From 843b3cdf24f71be37b0e844739cf5ec742674ae9 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Tue, 10 Aug 2010 21:56:29 -0700 Subject: [PATCH] more tests plus associated bug fixes, some tests still broken (in the sense they are not being run, not that they fail) --- src/jsx_eep0018.erl | 86 ++++++++++++++++++++++++++++++++++----------- src/jsx_format.erl | 2 ++ src/jsx_test.erl | 56 +++++++++++++++++++++++++---- 3 files changed, 117 insertions(+), 27 deletions(-) diff --git a/src/jsx_eep0018.erl b/src/jsx_eep0018.erl index bd2d604..cd9c280 100644 --- a/src/jsx_eep0018.erl +++ b/src/jsx_eep0018.erl @@ -127,7 +127,7 @@ collect({event, end_json, _Next}, [[Acc]], _Opts) -> %% the head of the accumulator and deal with it when we receive it's paired value collect({event, {key, _} = PreKey, Next}, [Current|_] = Acc, Opts) -> Key = event(PreKey, Opts), - case key_repeats(Key, Current) of + case decode_key_repeats(Key, Current) of true -> erlang:error(badarg) ; false -> collect(Next(), [Key] ++ Acc, Opts) end; @@ -141,6 +141,14 @@ collect({event, Event, Next}, [Current|Rest], Opts) when is_list(Current) -> collect({event, Event, Next}, [Key, Current|Rest], Opts) -> collect(Next(), [[{Key, event(Event, Opts)}] ++ Current] ++ Rest, Opts); +%% if our first returned event is {incomplete, ...} try to force end and return the +%% Event if one is returned +collect({incomplete, More}, [[]], Opts) -> + case More(end_stream) of + {event, Event, _Next} -> event(Event, Opts) + ; _ -> erlang:error(badarg) + end; + %% any other event is an error collect(_, _, _) -> erlang:error(badarg). @@ -172,6 +180,12 @@ event({float, Float}, _Opts) -> event({literal, Literal}, _Opts) -> Literal. + +decode_key_repeats(Key, [{Key, _Value}|_Rest]) -> true; +decode_key_repeats(Key, [_|Rest]) -> decode_key_repeats(Key, Rest); +decode_key_repeats(_Key, []) -> false. + + %% convert eep0018 representation to jsx events. note special casing for the empty object @@ -188,7 +202,7 @@ term_to_events(Term) -> proplist_to_events([{Key, Term}|Rest], Acc) -> Event = term_to_event(Term), EncodedKey = key_to_event(Key), - case key_repeats(EncodedKey, Acc) of + case encode_key_repeats(EncodedKey, Acc) of false -> proplist_to_events(Rest, Event ++ EncodedKey ++ Acc) ; true -> erlang:error(badarg) end; @@ -223,6 +237,16 @@ key_to_event(Key) when is_atom(Key) -> key_to_event(Key) when is_binary(Key) -> [{key, json_escape(Key)}]. + +encode_key_repeats([Key], SoFar) -> encode_key_repeats(Key, SoFar, 0). + +encode_key_repeats(Key, [Key|_], 0) -> true; +encode_key_repeats(Key, [end_object|Rest], Level) -> encode_key_repeats(Key, Rest, Level + 1); +encode_key_repeats(Key, [start_object|_], 0) -> false; +encode_key_repeats(Key, [start_object|Rest], Level) -> encode_key_repeats(Key, Rest, Level - 1); +encode_key_repeats(Key, [_|Rest], Level) -> encode_key_repeats(Key, Rest, Level); +encode_key_repeats(_, [], 0) -> false. + %% conversion of floats to 'nice' decimal output. erlang's float implementation is almost %% but not quite ieee 754. it converts negative zero to plain zero silently, and throws @@ -393,17 +417,49 @@ to_hex(10) -> $a; to_hex(X) -> X + $0. -%% common functions - -key_repeats([{key, Key}], [{key, Key}|_Rest]) -> true; -key_repeats(Key, [{Key, _Value}|_Rest]) -> true; -key_repeats(Key, [_|Rest]) -> key_repeats(Key, Rest); -key_repeats(_Key, []) -> false. - - %% eunit tests -ifdef(test). +decode_test_() -> + [ + {"empty object", ?_assert(json_to_term(<<"{}">>, []) =:= [{}])}, + {"empty array", ?_assert(json_to_term(<<"[]">>, []) =:= [])}, + {"simple object", ?_assert(json_to_term(<<"{\"a\": true, \"b\": true, \"c\": true}">>, [{label, atom}]) =:= [{a, true}, {b, true}, {c, true}])}, + {"simple array", ?_assert(json_to_term(<<"[true,true,true]">>, []) =:= [true, true, true])}, + {"nested structures", ?_assert(json_to_term(<<"{\"list\":[{\"list\":[{}, {}],\"object\":{}}, []],\"object\":{}}">>, [{label, atom}]) =:= [{list, [[{list, [[{}], [{}]]}, {object, [{}]}],[]]}, {object, [{}]}])}, + {"numbers", ?_assert(json_to_term(<<"[-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0]">>, []) =:= [-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0])}, + {"numbers (all floats)", ?_assert(json_to_term(<<"[-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0]">>, [{float, true}]) =:= [-10000000000.0, -1.0, 0.0, 0.0, 1.0, 10000000000.0, 1000000000.0])}, + {"strings", ?_assert(json_to_term(<<"[\"a string\"]">>, []) =:= [<<"a string">>])}, + {"literals", ?_assert(json_to_term(<<"[true,false,null]">>, []) =:= [true,false,null])}, + {"naked true", ?_assert(json_to_term(<<"true">>, [{strict, false}]) =:= true)}, + {"naked short number", ?_assert(json_to_term(<<"1">>, [{strict, false}]) =:= 1)}, + {"float", ?_assert(json_to_term(<<"1.0">>, [{strict, false}]) =:= 1.0)}, + {"naked string", ?_assert(json_to_term(<<"\"hello world\"">>, [{strict, false}]) =:= <<"hello world">>)}, + {"comments", ?_assert(json_to_term(<<"[ /* a comment in an empty array */ ]">>, [{comments, true}]) =:= [])} + ]. + +encode_test_() -> + [ + {"empty object", ?_assert(term_to_json([{}], []) =:= <<"{}">>)}, + {"empty array", ?_assert(term_to_json([], []) =:= <<"[]">>)}, + {"simple object", ?_assert(term_to_json([{a, true}, {b, true}, {c, true}], []) =:= <<"{\"a\":true,\"b\":true,\"c\":true}">>)}, + {"simple array", ?_assert(term_to_json([true, true, true], []) =:= <<"[true,true,true]">>)}, + {"nested structures", ?_assert(term_to_json([{list, [[{list, [[{}], [{}]]}, {object, [{}]}],[]]}, {object, [{}]}], []) =:= <<"{\"list\":[{\"list\":[{},{}],\"object\":{}},[]],\"object\":{}}">>)}, + {"numbers", ?_assert(term_to_json([-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0], []) =:= <<"[-1.0e10,-1,0.0,0,1,10000000000,1.0e9]">>)}, + {"strings", ?_assert(term_to_json([<<"a string">>], []) =:= <<"[\"a string\"]">>)}, + {"literals", ?_assert(term_to_json([true,false,null], []) =:= <<"[true,false,null]">>)}, + {"naked true", ?_assert(term_to_json(true, [{strict, false}]) =:= <<"true">>)}, + {"naked number", ?_assert(term_to_json(1, [{strict, false}]) =:= <<"1">>)}, + {"float", ?_assert(term_to_json(1.0, [{strict, false}]) =:= <<"1.0">>)}, + {"naked string", ?_assert(term_to_json(<<"hello world">>, [{strict, false}]) =:= <<"\"hello world\"">>)} + ]. + +repeated_keys_test_() -> + [ + {"encode", ?_assertError(badarg, term_to_json([{k, true}, {k, false}], []))}, + {"decode", ?_assertError(badarg, json_to_term(<<"{\"k\": true, \"k\": false}">>, []))} + ]. + escape_test_() -> [ {"json string escaping", ?_assert(json_escape(<<"\"\\\b\f\n\r\t">>) =:= <<"\\\"\\\\\\b\\f\\n\\r\\t">>)}, @@ -427,15 +483,5 @@ nice_decimal_test_() -> {"min denormalized float", ?_assert(float_to_decimal(math:pow(2, -1074)) =:= "5.0e-324")}, {"max denormalized float", ?_assert(float_to_decimal((1 - math:pow(2, -52)) * math:pow(2, -1022)) =:= "2.225073858507201e-308")} ]. - -key_repeats_test_() -> - [ - {"encoded key repeat", ?_assert(key_repeats([{key, <<"key">>}], [{key, <<>>}, {key, <<"notkey">>}, {key, <<"key">>}, {key, <<"trailing key">>}]) =:= true)}, - {"encoded key no repeat", ?_assert(key_repeats([{key, <<"key">>}], [{key, <<>>}, {key, <<"notkey">>}, {key, <<"trailing key">>}]) =:= false)}, - {"decoded key (atom) repeat", ?_assert(key_repeats(key, [{notkey, true}, {key, true}, {trailing_key, true}]) =:= true)}, - {"decoded key (binary) repeat", ?_assert(key_repeats(<<"key">>, [{<<"notkey">>, true}, {<<"key">>, true}, {<<"trailing key">>, true}]) =:= true)}, - {"decoded key (atom) no repeat", ?_assert(key_repeats(key, [{notkey, true}, {definitely_not_key, true}, {trailing_key, true}]) =:= false)}, - {"decoded key (binary) no repeat", ?_assert(key_repeats(<<"key">>, [{<<"notkey">>, true}, {<<"definitely not key">>, true}, {<<"trailing key">>, true}]) =:= false)} - ]. -endif. \ No newline at end of file diff --git a/src/jsx_format.erl b/src/jsx_format.erl index a43d650..bb2ccef 100644 --- a/src/jsx_format.erl +++ b/src/jsx_format.erl @@ -81,6 +81,8 @@ parse_opts([space|Rest], Opts) -> parse_opts(Rest, Opts#opts{space = 1}); parse_opts([{output_encoding, Val}|Rest], Opts) -> parse_opts(Rest, Opts#opts{output_encoding = Val}); +parse_opts([_|Rest], Opts) -> + parse_opts(Rest, Opts); parse_opts([], Opts) -> Opts. diff --git a/src/jsx_test.erl b/src/jsx_test.erl index dd507db..7386c57 100644 --- a/src/jsx_test.erl +++ b/src/jsx_test.erl @@ -42,20 +42,24 @@ test() -> erlang:error(notest). -else. jsx_decoder_test_() -> - jsx_decoder_gen(load_tests("./test/cases"), [utf8, utf16, {utf16, little}, utf32, {utf32, little}]). + jsx_decoder_gen(load_tests("./test/cases"), [utf8, utf16, {utf16, little}, utf32, {utf32, little}], fun decode/2). -jsx_decoder_gen([_Test|Rest], []) -> - jsx_decoder_gen(Rest, [utf8, utf16, {utf16, little}, utf32, {utf32, little}]); -jsx_decoder_gen([], _) -> +jsx_incremental_test_() -> + jsx_decoder_gen(load_tests("./test/cases"), [utf8, utf16, {utf16, little}, utf32, {utf32, little}], fun incremental_decode/2). + + +jsx_decoder_gen([_Test|Rest], [], F) -> + jsx_decoder_gen(Rest, [utf8, utf16, {utf16, little}, utf32, {utf32, little}], F); +jsx_decoder_gen([], _, _) -> []; -jsx_decoder_gen([Test|_] = Tests, [Encoding|Encodings]) -> +jsx_decoder_gen([Test|_] = Tests, [Encoding|Encodings], F) -> Name = lists:flatten(proplists:get_value(name, Test) ++ " :: " ++ io_lib:format("~p", [Encoding])), JSON = unicode:characters_to_binary(proplists:get_value(json, Test), unicode, Encoding), JSX = proplists:get_value(jsx, Test), Flags = proplists:get_value(jsx_flags, Test, []), {generator, fun() -> - [{Name, ?_assert(decode(JSON, Flags) =:= JSX)} | jsx_decoder_gen(Tests, Encodings)] + [{Name, ?_assert(F(JSON, Flags) =:= JSX)} | jsx_decoder_gen(Tests, Encodings, F)] end }. @@ -120,5 +124,43 @@ incremental_decode_loop({event, end_json, _Next}, _Rest, Acc) -> lists:reverse([end_json] ++ Acc); incremental_decode_loop({event, Event, Next}, Rest, Acc) -> incremental_decode_loop(Next(), Rest, [Event] ++ Acc). - + + +multi_decode_test_() -> + [ + {"multiple values in a single stream", ?_assert(multi_decode(multi_json_body(), []) =:= multi_test_result())} + ]. + + +multi_decode(JSON, Flags) -> + P = jsx:parser(Flags ++ [{multi_term, true}]), + multi_decode_loop(P(JSON), [[]]). + +multi_decode_loop({incomplete, _Next}, [[]|Acc]) -> + lists:reverse(Acc); +multi_decode_loop({event, end_json, Next}, [S|Acc]) -> + multi_decode_loop(Next(), [[]|[lists:reverse(S)] ++ Acc]); +multi_decode_loop({event, E, Next}, [S|Acc]) -> + multi_decode_loop(Next(), [[E] ++ S] ++ Acc). + + +multi_json_body() -> + <<"0 1 -1 1e1 0.7 0.7e-1 true false null {} [] [1, 2, 3] \"hope this works\"">>. + +multi_test_result() -> + [ [{integer, "0"}], + [{integer, "1"}], + [{integer, "-1"}], + [{float, "1.0e1"}], + [{float, "0.7"}], + [{float, "0.7e-1"}], + [{literal, true}], + [{literal, false}], + [{literal, null}], + [start_object, end_object], + [start_array, end_array], + [start_array, {integer, "1"}, {integer, "2"}, {integer, "3"}, end_array], + [{string, "hope this works"}] + ]. + -endif. \ No newline at end of file