summaryrefslogtreecommitdiff
path: root/src/couch/test/json_stream_parse_tests.erl
blob: ffcf9185acc8ae827baf1167ad5d2a7973f3ee8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
% 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]).