| % Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| % use this file except in compliance with the License. You may obtain a copy of |
| % the License at |
| % |
| % http://www.apache.org/licenses/LICENSE-2.0 |
| % |
| % Unless required by applicable law or agreed to in writing, software |
| % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| % License for the specific language governing permissions and limitations under |
| % the License. |
| |
| -module(json_stream_parse_tests). |
| |
| -include_lib("couch/include/couch_eunit.hrl"). |
| |
| -define(CASES, |
| [ |
| {1, "1", "integer numeric literial"}, |
| {3.1416, "3.14160", "float numeric literal"}, % text representation may truncate, trail zeroes |
| {-1, "-1", "negative integer numeric literal"}, |
| {-3.1416, "-3.14160", "negative float numeric literal"}, |
| {12.0e10, "1.20000e+11", "float literal in scientific notation"}, |
| {1.234E+10, "1.23400e+10", "another float literal in scientific notation"}, |
| {-1.234E-10, "-1.23400e-10", "negative float literal in scientific notation"}, |
| {10.0, "1.0e+01", "yet another float literal in scientific notation"}, |
| {123.456, "1.23456E+2", "yet another float literal in scientific notation"}, |
| {10.0, "1e1", "yet another float literal in scientific notation"}, |
| {<<"foo">>, "\"foo\"", "string literal"}, |
| {<<"foo", 5, "bar">>, "\"foo\\u0005bar\"", "string literal with \\u0005"}, |
| {<<"">>, "\"\"", "empty string literal"}, |
| {<<"\n\n\n">>, "\"\\n\\n\\n\"", "only new lines literal"}, |
| {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\"", |
| "only white spaces string literal"}, |
| {null, "null", "null literal"}, |
| {true, "true", "true literal"}, |
| {false, "false", "false literal"}, |
| {<<"null">>, "\"null\"", "null string literal"}, |
| {<<"true">>, "\"true\"", "true string literal"}, |
| {<<"false">>, "\"false\"", "false string literal"}, |
| {{[]}, "{}", "empty object literal"}, |
| {{[{<<"foo">>, <<"bar">>}]}, "{\"foo\":\"bar\"}", |
| "simple object literal"}, |
| {{[{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]}, |
| "{\"foo\":\"bar\",\"baz\":123}", "another simple object literal"}, |
| {[], "[]", "empty array literal"}, |
| {[[]], "[[]]", "empty array literal inside a single element array literal"}, |
| {[1, <<"foo">>], "[1,\"foo\"]", "simple non-empty array literal"}, |
| {[1199344435545.0, 1], "[1199344435545.0,1]", |
| "another simple non-empty array literal"}, |
| {[false, true, 321, null], "[false, true, 321, null]", "array of literals"}, |
| {{[{<<"foo">>, [123]}]}, "{\"foo\":[123]}", |
| "object literal with an array valued property"}, |
| {{[{<<"foo">>, {[{<<"bar">>, true}]}}]}, |
| "{\"foo\":{\"bar\":true}}", "nested object literal"}, |
| {{[{<<"foo">>, []}, {<<"bar">>, {[{<<"baz">>, true}]}}, |
| {<<"alice">>, <<"bob">>}]}, |
| "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}", |
| "complex object literal"}, |
| {[-123, <<"foo">>, {[{<<"bar">>, []}]}, null], |
| "[-123,\"foo\",{\"bar\":[]},null]", |
| "complex array literal"} |
| ] |
| ). |
| |
| |
| raw_json_input_test_() -> |
| Tests = lists:map( |
| fun({EJson, JsonString, Desc}) -> |
| {Desc, |
| ?_assert(equiv(EJson, json_stream_parse:to_ejson(JsonString)))} |
| end, ?CASES), |
| {"Tests with raw JSON string as the input", Tests}. |
| |
| one_byte_data_fun_test_() -> |
| Tests = lists:map( |
| fun({EJson, JsonString, Desc}) -> |
| DataFun = fun() -> single_byte_data_fun(JsonString) end, |
| {Desc, |
| ?_assert(equiv(EJson, json_stream_parse:to_ejson(DataFun)))} |
| end, ?CASES), |
| {"Tests with a 1 byte output data function as the input", Tests}. |
| |
| test_multiple_bytes_data_fun_test_() -> |
| Tests = lists:map( |
| fun({EJson, JsonString, Desc}) -> |
| DataFun = fun() -> multiple_bytes_data_fun(JsonString) end, |
| {Desc, |
| ?_assert(equiv(EJson, json_stream_parse:to_ejson(DataFun)))} |
| end, ?CASES), |
| {"Tests with a multiple bytes output data function as the input", Tests}. |
| |
| |
| %% Test for equivalence of Erlang terms. |
| %% Due to arbitrary order of construction, equivalent objects might |
| %% compare unequal as erlang terms, so we need to carefully recurse |
| %% through aggregates (tuples and objects). |
| equiv({Props1}, {Props2}) -> |
| equiv_object(Props1, Props2); |
| equiv(L1, L2) when is_list(L1), is_list(L2) -> |
| equiv_list(L1, L2); |
| equiv(N1, N2) when is_number(N1), is_number(N2) -> |
| N1 == N2; |
| equiv(B1, B2) when is_binary(B1), is_binary(B2) -> |
| B1 == B2; |
| equiv(true, true) -> |
| true; |
| equiv(false, false) -> |
| true; |
| equiv(null, null) -> |
| true. |
| |
| %% Object representation and traversal order is unknown. |
| %% Use the sledgehammer and sort property lists. |
| equiv_object(Props1, Props2) -> |
| L1 = lists:keysort(1, Props1), |
| L2 = lists:keysort(1, Props2), |
| Pairs = lists:zip(L1, L2), |
| true = lists:all( |
| fun({{K1, V1}, {K2, V2}}) -> |
| equiv(K1, K2) andalso equiv(V1, V2) |
| end, |
| Pairs). |
| |
| %% Recursively compare tuple elements for equivalence. |
| equiv_list([], []) -> |
| true; |
| equiv_list([V1 | L1], [V2 | L2]) -> |
| equiv(V1, V2) andalso equiv_list(L1, L2). |
| |
| single_byte_data_fun([]) -> |
| done; |
| single_byte_data_fun([H | T]) -> |
| {<<H>>, fun() -> single_byte_data_fun(T) end}. |
| |
| multiple_bytes_data_fun([]) -> |
| done; |
| multiple_bytes_data_fun(L) -> |
| N = crypto:rand_uniform(0, 7), |
| {Part, Rest} = split(L, N), |
| {list_to_binary(Part), fun() -> multiple_bytes_data_fun(Rest) end}. |
| |
| split(L, N) when length(L) =< N -> |
| {L, []}; |
| split(L, N) -> |
| take(N, L, []). |
| |
| take(0, L, Acc) -> |
| {lists:reverse(Acc), L}; |
| take(N, [H|L], Acc) -> |
| take(N - 1, L, [H | Acc]). |