rewrote test harness to use eunit and make it easier to add future tests
This commit is contained in:
parent
f5f1f588d4
commit
ab67abd01a
37 changed files with 121 additions and 81 deletions
33
src/jsx.erl
33
src/jsx.erl
|
@ -1,17 +1,26 @@
|
||||||
-module(jsx).
|
-module(jsx).
|
||||||
|
|
||||||
-export([decoder/0, decoder/1]).
|
-export([decoder/0, decoder/2]).
|
||||||
|
|
||||||
-include("jsx_common.hrl").
|
-include("jsx_common.hrl").
|
||||||
|
|
||||||
decoder() ->
|
decoder() ->
|
||||||
decoder([]).
|
decoder(none, []).
|
||||||
|
|
||||||
decoder(OptsList) ->
|
decoder(Callbacks, OptsList) ->
|
||||||
OptsRec = parse_opts(OptsList),
|
Opts = parse_opts(OptsList),
|
||||||
case OptsRec#opts.encoding of
|
case Opts#opts.encoding of
|
||||||
utf8 ->
|
utf8 ->
|
||||||
fun(Stream) -> jsx_utf8:start(Stream, [], [], OptsRec) end
|
fun(Stream) -> jsx_utf8:start(Stream, [], init_callbacks(Callbacks), Opts) end
|
||||||
|
; utf16-big ->
|
||||||
|
fun(Stream) -> jsx_utf16b:start(Stream, [], init_callbacks(Callbacks), Opts) end
|
||||||
|
; utf16-little ->
|
||||||
|
fun(Stream) -> jsx_utf16l:start(Stream, [], init_callbacks(Callbacks), Opts) end
|
||||||
|
; utf32-big ->
|
||||||
|
fun(Stream) -> jsx_utf32b:start(Stream, [], init_callbacks(Callbacks), Opts) end
|
||||||
|
; utf32-little ->
|
||||||
|
fun(Stream) -> jsx_utf32l:start(Stream, [], init_callbacks(Callbacks), Opts) end
|
||||||
|
;
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,5 +39,13 @@ parse_opts([{naked_values, Value}|Rest], Opts) ->
|
||||||
true = lists:member(Value, [true, false]),
|
true = lists:member(Value, [true, false]),
|
||||||
parse_opts(Rest, Opts#opts{naked_values = Value});
|
parse_opts(Rest, Opts#opts{naked_values = Value});
|
||||||
parse_opts([{encoding, Value}|Rest], Opts) ->
|
parse_opts([{encoding, Value}|Rest], Opts) ->
|
||||||
true = lists:member(Value, [utf8]),
|
true = lists:member(Value, [auto, utf8, utf16-big, utf16-little, utf32-big, utf32-little]),
|
||||||
parse_opts(Rest, Opts#opts{encoding = Value}).
|
parse_opts(Rest, Opts#opts{encoding = Value}).
|
||||||
|
|
||||||
|
init_callbacks(none) ->
|
||||||
|
{none, []};
|
||||||
|
init_callbacks({M, S}) when is_atom(M) ->
|
||||||
|
{M, S};
|
||||||
|
init_callbacks({F, S}) when is_function(F) ->
|
||||||
|
{F, S}.
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,15 @@
|
||||||
-export([start/4]).
|
-export([start/4]).
|
||||||
|
|
||||||
-include("jsx_common.hrl").
|
-include("jsx_common.hrl").
|
||||||
|
|
||||||
|
|
||||||
callback(eof, Callbacks) ->
|
|
||||||
lists:reverse(Callbacks);
|
|
||||||
callback(Event, Callbacks) ->
|
|
||||||
[Event] ++ Callbacks.
|
|
||||||
|
|
||||||
|
|
||||||
%% this code is mostly autogenerated and mostly ugly. apologies. for more insight on
|
%% this code is mostly autogenerated and mostly ugly. apologies. for more insight on
|
||||||
%% Callbacks or Opts, see the comments accompanying callback/2 (in this file) and
|
%% Callbacks or Opts, see the comments accompanying decoder/2 (in jsx.erl). Stack
|
||||||
%% parse_opts/1 (in jsx.erl). Stack is a stack of flags used to track depth and to
|
%% is a stack of flags used to track depth and to keep track of whether we are
|
||||||
%% keep track of whether we are returning from a value or a key inside objects. all
|
%% returning from a value or a key inside objects. all pops, peeks and pushes are
|
||||||
%% pops, peeks and pushes are inlined. the code that handles naked values and comments
|
%% inlined. the code that handles naked values and comments is not optimized by the
|
||||||
%% is not optimized by the compiler for efficient matching, but you shouldn't be using
|
%% compiler for efficient matching, but you shouldn't be using naked values or comments
|
||||||
%% naked values or comments anyways, they are horrible and contrary to the spec.
|
%% anyways, they are horrible and contrary to the spec.
|
||||||
|
|
||||||
start(<<?start_object/utf8, Rest/binary>>, Stack, Callbacks, Opts) ->
|
start(<<?start_object/utf8, Rest/binary>>, Stack, Callbacks, Opts) ->
|
||||||
object(Rest, [key|Stack], callback(start_object, Callbacks), Opts);
|
object(Rest, [key|Stack], callback(start_object, Callbacks), Opts);
|
||||||
|
@ -204,7 +198,7 @@ escaped_unicode(<<D/utf8, Rest/binary>>, Stack, Callbacks, Opts, String, [C, B,
|
||||||
string(Rest, Stack, Callbacks, Opts, [X] ++ String)
|
string(Rest, Stack, Callbacks, Opts, [X] ++ String)
|
||||||
; codepoint ->
|
; codepoint ->
|
||||||
string(Rest, Stack, Callbacks, Opts, [X] ++ String)
|
string(Rest, Stack, Callbacks, Opts, [X] ++ String)
|
||||||
; none ->
|
; _ ->
|
||||||
string(Rest, Stack, Callbacks, Opts, [?rsolidus, $u, A, B, C, D] ++ String)
|
string(Rest, Stack, Callbacks, Opts, [?rsolidus, $u, A, B, C, D] ++ String)
|
||||||
end;
|
end;
|
||||||
escaped_unicode(<<S/utf8, Rest/binary>>, Stack, Callbacks, Opts, String, Acc) when ?is_hex(S) ->
|
escaped_unicode(<<S/utf8, Rest/binary>>, Stack, Callbacks, Opts, String, Acc) when ?is_hex(S) ->
|
||||||
|
@ -422,6 +416,18 @@ maybe_comment_done(<<?solidus/utf8, Rest/binary>>, Resume) ->
|
||||||
Resume(Rest);
|
Resume(Rest);
|
||||||
maybe_comment_done(<<>>, Resume) ->
|
maybe_comment_done(<<>>, Resume) ->
|
||||||
fun(Stream) -> maybe_comment_done(Stream, Resume) end.
|
fun(Stream) -> maybe_comment_done(Stream, Resume) end.
|
||||||
|
|
||||||
|
|
||||||
|
%% helper function for dispatching of parser events
|
||||||
|
|
||||||
|
callback(eof, {none, Callbacks}) ->
|
||||||
|
lists:reverse(Callbacks);
|
||||||
|
callback(Event, {none, Callbacks}) ->
|
||||||
|
{none, [Event] ++ Callbacks};
|
||||||
|
callback(Event, {Mod, State}) when is_atom(Mod) ->
|
||||||
|
{Mod, Mod:jsx_event(Event, State)};
|
||||||
|
callback(Event, {F, State}) when is_function(F) ->
|
||||||
|
{F, F(Event, State)}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
1
test/cases/absurdly_deep_array.json
Normal file
1
test/cases/absurdly_deep_array.json
Normal file
File diff suppressed because one or more lines are too long
1
test/cases/absurdly_deep_array.test
Normal file
1
test/cases/absurdly_deep_array.test
Normal file
File diff suppressed because one or more lines are too long
1
test/cases/array.json
Normal file
1
test/cases/array.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["foo","bar", "baz",true,false,null,{"key":"value"},[null,null,null,[]],"\n\r\\"]
|
1
test/cases/array.test
Normal file
1
test/cases/array.test
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[start_array, {string, "foo"}, {string, "bar"}, {string, "baz"}, {literal, true}, {literal, false}, {literal, null}, start_object, {key, "key"}, {string, "value"}, end_object, start_array, {literal, null}, {literal, null}, {literal, null}, start_array, end_array, end_array, {string, "\n\r\\"}, end_array].
|
1
test/cases/comments.json
Normal file
1
test/cases/comments.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[/*array open comment*/ "a string"/*string comment*/, 1/*number comment*/, {/*object open comment*/"key"/*post key comment*/:/*colon comment*/[]/*object close comment*/}, true/*literal comment*/]
|
2
test/cases/comments.test
Normal file
2
test/cases/comments.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[start_array, {string, "a string"}, {number, "1"}, start_object, {key, "key"}, start_array, end_array, end_object, {literal, true}, end_array].
|
||||||
|
[{comments, true}].
|
1
test/cases/deep_array.json
Normal file
1
test/cases/deep_array.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[[[]]]
|
1
test/cases/deep_array.test
Normal file
1
test/cases/deep_array.test
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[start_array, start_array, start_array, end_array, end_array, end_array].
|
1
test/cases/empty_array.json
Normal file
1
test/cases/empty_array.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[]
|
1
test/cases/empty_array.test
Normal file
1
test/cases/empty_array.test
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[start_array, end_array].
|
1
test/cases/empty_array_with_comment.json
Normal file
1
test/cases/empty_array_with_comment.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ /* this is a comment and should be ignored */ ]
|
3
test/cases/empty_array_with_comment.test
Normal file
3
test/cases/empty_array_with_comment.test
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[start_array, end_array].
|
||||||
|
|
||||||
|
[{comments, true}].
|
1
test/cases/empty_object.json
Normal file
1
test/cases/empty_object.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
1
test/cases/empty_object.test
Normal file
1
test/cases/empty_object.test
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[start_object, end_object].
|
1
test/cases/empty_object_with_comment.json
Normal file
1
test/cases/empty_object_with_comment.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{/*comment*/}
|
3
test/cases/empty_object_with_comment.test
Normal file
3
test/cases/empty_object_with_comment.test
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[start_object, end_object].
|
||||||
|
|
||||||
|
[{comments, true}].
|
1
test/cases/naked_false.json
Normal file
1
test/cases/naked_false.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
false
|
2
test/cases/naked_false.test
Normal file
2
test/cases/naked_false.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[{literal, false}].
|
||||||
|
[{naked_values, true}].
|
1
test/cases/naked_null.json
Normal file
1
test/cases/naked_null.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
null
|
2
test/cases/naked_null.test
Normal file
2
test/cases/naked_null.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[{literal, null}].
|
||||||
|
[{naked_values, true}].
|
1
test/cases/naked_number.json
Normal file
1
test/cases/naked_number.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
42
|
2
test/cases/naked_number.test
Normal file
2
test/cases/naked_number.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[{number, "42"}].
|
||||||
|
[{naked_values, true}].
|
1
test/cases/naked_string.json
Normal file
1
test/cases/naked_string.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"this is a naked string"
|
2
test/cases/naked_string.test
Normal file
2
test/cases/naked_string.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[{string, "this is a naked string"}].
|
||||||
|
[{naked_values, true}].
|
1
test/cases/naked_true.json
Normal file
1
test/cases/naked_true.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
true
|
2
test/cases/naked_true.test
Normal file
2
test/cases/naked_true.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[{literal, true}].
|
||||||
|
[{naked_values, true}].
|
1
test/cases/numbers.json
Normal file
1
test/cases/numbers.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 42, 127, 99999999999999999999999999999, 1e1, 1E1, 1.0e1, 1.325e478534, -1, -1e-1, 3.7e-57834235 ]
|
1
test/cases/numbers.test
Normal file
1
test/cases/numbers.test
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ start_array, {number, "1"}, {number, "2"}, {number, "3"}, {number, "4"}, {number, "5"}, {number, "6"}, {number, "7"}, {number, "8"}, {number, "9"}, {number, "42"}, {number, "127"}, {number, "99999999999999999999999999999"}, {number, "1e1"}, {number, "1e1"}, {number, "1.0e1"}, {number, "1.325e478534"}, {number, "-1"}, {number, "-1e-1"}, {number, "3.7e-57834235"}, end_array ].
|
1
test/cases/string.json
Normal file
1
test/cases/string.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ "this is a random string with \n embedded\u0020escapes in it" ]
|
1
test/cases/string.test
Normal file
1
test/cases/string.test
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[start_array, {string, "this is a random string with \n embedded escapes in it"}, end_array].
|
1
test/cases/string_escapes.json
Normal file
1
test/cases/string_escapes.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["\"", "\\", "\/", "\b", "\f", "\n", "\r", "\t"]
|
3
test/cases/string_escapes.test
Normal file
3
test/cases/string_escapes.test
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[start_array, {string, "\""}, {string, "\\"}, {string, "/"}, {string, "\b"}, {string, "\f"}, {string, "\n"}, {string, "\r"}, {string, "\t"}, end_array].
|
||||||
|
|
||||||
|
|
1
test/cases/unicode.json
Normal file
1
test/cases/unicode.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ "arabic letter alef: ", "\u0627" ]
|
2
test/cases/unicode.test
Normal file
2
test/cases/unicode.test
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[start_array, {string, "arabic letter alef: "}, {string, [16#0627]}, end_array].
|
||||||
|
[{escaped_unicode, codepoint}].
|
|
@ -1,66 +1,36 @@
|
||||||
-module(jsx_test).
|
-module(jsx_test).
|
||||||
|
|
||||||
-export([test/2]).
|
-export([test/1]).
|
||||||
|
|
||||||
test(TestsDir, Opts) ->
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
{ok, MaybeTests} = file:list_dir(TestsDir),
|
|
||||||
|
|
||||||
TestSpecs = lists:map(fun(X) ->
|
|
||||||
filename:basename(X, ".test") end,
|
|
||||||
lists:filter(fun(X) ->
|
|
||||||
case filename:extension(X) of
|
|
||||||
".test" -> true
|
|
||||||
; _ -> false
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
MaybeTests)),
|
|
||||||
|
|
||||||
Results = test(TestSpecs, TestsDir, {[], [], []}),
|
|
||||||
|
|
||||||
output(Results, Opts).
|
|
||||||
|
|
||||||
test([], _, Results) ->
|
|
||||||
Results;
|
|
||||||
test([Test|Rest], Dir, {Pass, Fail, Discard}) ->
|
|
||||||
{DecoderOpts, Expected} = case file:consult(filename:nativename(Dir ++ "/" ++ Test ++ ".test")) of
|
|
||||||
{ok, [Result]} when is_list(Result) -> {[], Result}
|
|
||||||
; {ok, {Opts, Result}} -> {Opts, Result}
|
|
||||||
end,
|
|
||||||
|
|
||||||
try decode(Test, Dir, DecoderOpts) of
|
|
||||||
Object ->
|
|
||||||
case Object == Expected of
|
|
||||||
true -> test(Rest, Dir, {[Test] ++ Pass, Fail, Discard})
|
|
||||||
; false -> test(Rest, Dir, {Pass, [Test] ++ Fail, Discard})
|
|
||||||
end
|
|
||||||
catch
|
|
||||||
error:function_clause ->
|
|
||||||
test(Rest, Dir, {Pass, Fail, [Test] ++ Discard})
|
|
||||||
; error:enoent ->
|
|
||||||
test(Rest, Dir, {Pass, Fail, [Test] ++ Discard})
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
decode(Test, Dir, DecoderOpts) ->
|
test(Dir) ->
|
||||||
Decoder = jsx:decoder(DecoderOpts),
|
Tests = gen_tests(Dir),
|
||||||
{ok, JSON} = file:read_file(filename:nativename(Dir ++ "/" ++ Test ++ ".json")),
|
eunit:test(Tests).
|
||||||
|
|
||||||
Decoder(JSON).
|
gen_tests(Dir) ->
|
||||||
|
TestSpecs = filelib:wildcard("*.test", Dir),
|
||||||
|
gen_tests(TestSpecs, Dir, []).
|
||||||
output({Pass, Fail, Discard}, Opts) ->
|
|
||||||
Passes = length(Pass),
|
gen_tests([], _, Acc) ->
|
||||||
Failures = length(Fail),
|
lists:reverse(Acc);
|
||||||
Discards = length(Discard),
|
|
||||||
|
gen_tests([Test|Rest], Dir, Acc) ->
|
||||||
Total = Passes + Failures + Discards,
|
gen_tests(Rest, Dir, test_body(Test, Dir) ++ Acc).
|
||||||
|
|
||||||
io:format("***~p total tests run, ~p passes, ~p failures, ~p discarded~n Failed:~n", [Total, Passes, Failures, Discards]),
|
test_body(TestSpec, Dir) ->
|
||||||
lists:foreach(fun(X) -> io:format(" ~p~n", [X]) end, Fail).
|
try
|
||||||
|
TestName = filename:basename(TestSpec, ".test"),
|
||||||
|
{ok, JSON} = file:read_file(Dir ++ "/" ++ TestName ++ ".json"),
|
||||||
|
case file:consult(Dir ++ "/" ++ TestSpec) of
|
||||||
|
{ok, [Events]} ->
|
||||||
|
Decoder = jsx:decoder(),
|
||||||
|
[{TestName, ?_assertEqual(Decoder(JSON), Events)}]
|
||||||
|
; {ok, [Events, Flags]} ->
|
||||||
|
Decoder = jsx:decoder(none, Flags),
|
||||||
|
[{TestName, ?_assertEqual(Decoder(JSON), Events)}]
|
||||||
|
end
|
||||||
|
catch _:_ -> []
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue