diff --git a/ebin/jsx.app b/ebin/jsx.app index 587d475..9dfe537 100644 --- a/ebin/jsx.app +++ b/ebin/jsx.app @@ -11,8 +11,7 @@ jsx_utf32le, jsx_eep0018, jsx_format, - jsx_verify, - jsx_test + jsx_verify ]}, {registered, []}, {applications, [ diff --git a/src/jsx.erl b/src/jsx.erl index 16487f4..4febd49 100644 --- a/src/jsx.erl +++ b/src/jsx.erl @@ -41,6 +41,11 @@ -include("./include/jsx_common.hrl"). +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + + %% @type jsx_parser() = (binary()) -> jsx_parser_result(). %% @type jsx_parser_result() = {event, jsx_event(), (() -> jsx_parser_result())} @@ -519,4 +524,136 @@ detect_encoding(Bin, Opts) -> fun(end_stream) -> {error, badjson} ; (Stream) -> detect_encoding(<>, Opts) end - }. \ No newline at end of file + }. + + +-ifdef(TEST). + +jsx_decoder_test_() -> + jsx_decoder_gen(load_tests(?eunit_test_path)). + + +jsx_decoder_gen([]) -> []; +jsx_decoder_gen(Tests) -> jsx_decoder_gen(Tests, [utf8, utf16, {utf16, little}, utf32, {utf32, little}]). + +jsx_decoder_gen([_Test|Rest], []) -> + jsx_decoder_gen(Rest); +jsx_decoder_gen([Test|_] = Tests, [Encoding|Encodings]) -> + 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)} + | {generator, + fun() -> [{Name ++ " incremental", ?_assert(incremental_decode(JSON, Flags) =:= JSX)} + | jsx_decoder_gen(Tests, Encodings)] + end + } + ] + end + }. + + +load_tests(Path) -> + %% search the specified directory for any files with the .test ending + TestSpecs = filelib:wildcard("*.test", Path), + load_tests(TestSpecs, Path, []). + +load_tests([], _Dir, Acc) -> + lists:reverse(Acc); +load_tests([Test|Rest], Dir, Acc) -> + %% should alert about badly formed tests eventually, but for now just skip over them + case file:consult(Dir ++ "/" ++ Test) of + {ok, TestSpec} -> + try + ParsedTest = parse_tests(TestSpec, Dir), + load_tests(Rest, Dir, [ParsedTest] ++ Acc) + catch _:_ -> + load_tests(Rest, Dir, Acc) + end + ; {error, _Reason} -> + load_tests(Rest, Dir, Acc) + end. + + +parse_tests(TestSpec, Dir) -> + parse_tests(TestSpec, Dir, []). + +parse_tests([{json, Path}|Rest], Dir, Acc) when is_list(Path) -> + case file:read_file(Dir ++ "/" ++ Path) of + {ok, Bin} -> parse_tests(Rest, Dir, [{json, Bin}] ++ Acc) + ; _ -> erlang:error(badarg) + end; +parse_tests([KV|Rest], Dir, Acc) -> + parse_tests(Rest, Dir, [KV] ++ Acc); +parse_tests([], _Dir, Acc) -> + Acc. + + +decode(JSON, Flags) -> + P = jsx:parser(Flags), + decode_loop(P(JSON), []). + +decode_loop({event, end_json, _Next}, Acc) -> + lists:reverse([end_json] ++ Acc); +decode_loop({incomplete, More}, Acc) -> + decode_loop(More(end_stream), Acc); +decode_loop({event, E, Next}, Acc) -> + decode_loop(Next(), [E] ++ Acc). + + +incremental_decode(<>, Flags) -> + P = jsx:parser(Flags), + incremental_decode_loop(P(C), Rest, []). + +incremental_decode_loop({incomplete, Next}, <<>>, Acc) -> + incremental_decode_loop(Next(end_stream), <<>>, Acc); +incremental_decode_loop({incomplete, Next}, <>, Acc) -> + incremental_decode_loop(Next(C), Rest, Acc); +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 truefalsenull {} {\"key\": \"value\"}[] [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_object, {key, "key"}, {string, "value"}, 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 diff --git a/src/jsx_test.erl b/src/jsx_test.erl deleted file mode 100644 index 4a2003c..0000000 --- a/src/jsx_test.erl +++ /dev/null @@ -1,175 +0,0 @@ -%% The MIT License - -%% Copyright (c) 2010 Alisdair Sullivan - -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -%% THE SOFTWARE. - - -%% @hidden hide this module from edoc, exported functions are internal to jsx -%% and may be altered or removed without notice - - --module(jsx_test). - - --ifndef(TEST). --export([test/0]). --endif. - - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). --endif. - - - -%% if not compiled with test support --ifndef(TEST). -test() -> erlang:error(notest). --else. - - -jsx_decoder_test_() -> - jsx_decoder_gen(load_tests(?eunit_test_path)). - - -jsx_decoder_gen([]) -> []; -jsx_decoder_gen(Tests) -> jsx_decoder_gen(Tests, [utf8, utf16, {utf16, little}, utf32, {utf32, little}]). - -jsx_decoder_gen([_Test|Rest], []) -> - jsx_decoder_gen(Rest); -jsx_decoder_gen([Test|_] = Tests, [Encoding|Encodings]) -> - 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)} - | {generator, - fun() -> [{Name ++ " incremental", ?_assert(incremental_decode(JSON, Flags) =:= JSX)} - | jsx_decoder_gen(Tests, Encodings)] - end - } - ] - end - }. - - -load_tests(Path) -> - %% search the specified directory for any files with the .test ending - TestSpecs = filelib:wildcard("*.test", Path), - load_tests(TestSpecs, Path, []). - -load_tests([], _Dir, Acc) -> - lists:reverse(Acc); -load_tests([Test|Rest], Dir, Acc) -> - %% should alert about badly formed tests eventually, but for now just skip over them - case file:consult(Dir ++ "/" ++ Test) of - {ok, TestSpec} -> - try - ParsedTest = parse_tests(TestSpec, Dir), - load_tests(Rest, Dir, [ParsedTest] ++ Acc) - catch _:_ -> - load_tests(Rest, Dir, Acc) - end - ; {error, _Reason} -> - load_tests(Rest, Dir, Acc) - end. - - -parse_tests(TestSpec, Dir) -> - parse_tests(TestSpec, Dir, []). - -parse_tests([{json, Path}|Rest], Dir, Acc) when is_list(Path) -> - case file:read_file(Dir ++ "/" ++ Path) of - {ok, Bin} -> parse_tests(Rest, Dir, [{json, Bin}] ++ Acc) - ; _ -> erlang:error(badarg) - end; -parse_tests([KV|Rest], Dir, Acc) -> - parse_tests(Rest, Dir, [KV] ++ Acc); -parse_tests([], _Dir, Acc) -> - Acc. - - -decode(JSON, Flags) -> - P = jsx:parser(Flags), - decode_loop(P(JSON), []). - -decode_loop({event, end_json, _Next}, Acc) -> - lists:reverse([end_json] ++ Acc); -decode_loop({incomplete, More}, Acc) -> - decode_loop(More(end_stream), Acc); -decode_loop({event, E, Next}, Acc) -> - decode_loop(Next(), [E] ++ Acc). - - -incremental_decode(<>, Flags) -> - P = jsx:parser(Flags), - incremental_decode_loop(P(C), Rest, []). - -incremental_decode_loop({incomplete, Next}, <<>>, Acc) -> - incremental_decode_loop(Next(end_stream), <<>>, Acc); -incremental_decode_loop({incomplete, Next}, <>, Acc) -> - incremental_decode_loop(Next(C), Rest, Acc); -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 truefalsenull {} {\"key\": \"value\"}[] [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_object, {key, "key"}, {string, "value"}, 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