Updated Branches: refs/heads/1843-replace-ejson-with-jiffy [created] 2e6092e4e
http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/simple.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/simple.json b/src/jiffy/test/cases/simple.json new file mode 100644 index 0000000..9ed80c9 --- /dev/null +++ b/src/jiffy/test/cases/simple.json @@ -0,0 +1,5 @@ +{ + "this": "is", + "really": "simple", + "json": "right?" +} http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_invalid_escape.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_invalid_escape.eterm b/src/jiffy/test/cases/string_invalid_escape.eterm new file mode 100644 index 0000000..eb20890 --- /dev/null +++ b/src/jiffy/test/cases/string_invalid_escape.eterm @@ -0,0 +1 @@ +{error,{63,invalid_string}}. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_invalid_escape.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_invalid_escape.json b/src/jiffy/test/cases/string_invalid_escape.json new file mode 100644 index 0000000..54a38ac --- /dev/null +++ b/src/jiffy/test/cases/string_invalid_escape.json @@ -0,0 +1 @@ +["\n foo \/ bar \r\f\\\ufffd\t\b\"\\ and you can't escape thi\s"] http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_invalid_hex_char.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_invalid_hex_char.eterm b/src/jiffy/test/cases/string_invalid_hex_char.eterm new file mode 100644 index 0000000..51de423 --- /dev/null +++ b/src/jiffy/test/cases/string_invalid_hex_char.eterm @@ -0,0 +1 @@ +{error,{44,invalid_string}}. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_invalid_hex_char.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_invalid_hex_char.json b/src/jiffy/test/cases/string_invalid_hex_char.json new file mode 100644 index 0000000..bde7ee9 --- /dev/null +++ b/src/jiffy/test/cases/string_invalid_hex_char.json @@ -0,0 +1 @@ +"foo foo, blah blah \u0123 \u4567 \u89ab \uc/ef \uABCD \uEFFE bar baz bing" http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_with_escapes.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_with_escapes.eterm b/src/jiffy/test/cases/string_with_escapes.eterm new file mode 100644 index 0000000..51f7f25 --- /dev/null +++ b/src/jiffy/test/cases/string_with_escapes.eterm @@ -0,0 +1,5 @@ +[ + <<"\n foo \/ bar \r\f\\", 239, 191, 186, "\t\b\"\\">>, + <<"\"and this string has an escape at the beginning">>, + <<"and this string has no escapes">> +]. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_with_escapes.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_with_escapes.json b/src/jiffy/test/cases/string_with_escapes.json new file mode 100644 index 0000000..532b5f4 --- /dev/null +++ b/src/jiffy/test/cases/string_with_escapes.json @@ -0,0 +1,3 @@ +["\n foo \/ bar \r\f\\\ufffa\t\b\"\\", + "\"and this string has an escape at the beginning", + "and this string has no escapes" ] http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_with_invalid_newline.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_with_invalid_newline.eterm b/src/jiffy/test/cases/string_with_invalid_newline.eterm new file mode 100644 index 0000000..626a2f4 --- /dev/null +++ b/src/jiffy/test/cases/string_with_invalid_newline.eterm @@ -0,0 +1 @@ +{error,{67,invalid_string}}. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/string_with_invalid_newline.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/string_with_invalid_newline.json b/src/jiffy/test/cases/string_with_invalid_newline.json new file mode 100644 index 0000000..2756f26 --- /dev/null +++ b/src/jiffy/test/cases/string_with_invalid_newline.json @@ -0,0 +1,2 @@ +"la di dah. this is a string, and I can do this, \n, but not this +" \ No newline at end of file http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/three_byte_utf8.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/three_byte_utf8.eterm b/src/jiffy/test/cases/three_byte_utf8.eterm new file mode 100644 index 0000000..111676b --- /dev/null +++ b/src/jiffy/test/cases/three_byte_utf8.eterm @@ -0,0 +1,4 @@ +{[ + {<<"matzue">>, <<230, 157, 190, 230, 177, 159>>}, + {<<"asakusa">>, <<230, 181, 133, 232, 141, 137>>} +]}. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/three_byte_utf8.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/three_byte_utf8.json b/src/jiffy/test/cases/three_byte_utf8.json new file mode 100644 index 0000000..9c9e656 --- /dev/null +++ b/src/jiffy/test/cases/three_byte_utf8.json @@ -0,0 +1 @@ +{"matzue": "æ¾æ±", "asakusa": "æµ è"} http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/true.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/true.eterm b/src/jiffy/test/cases/true.eterm new file mode 100644 index 0000000..60c0d88 --- /dev/null +++ b/src/jiffy/test/cases/true.eterm @@ -0,0 +1 @@ +true. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/true.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/true.json b/src/jiffy/test/cases/true.json new file mode 100644 index 0000000..27ba77d --- /dev/null +++ b/src/jiffy/test/cases/true.json @@ -0,0 +1 @@ +true http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/true_then_garbage.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/true_then_garbage.eterm b/src/jiffy/test/cases/true_then_garbage.eterm new file mode 100644 index 0000000..30b0113 --- /dev/null +++ b/src/jiffy/test/cases/true_then_garbage.eterm @@ -0,0 +1 @@ +{error,{5,invalid_trailing_data}}. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/true_then_garbage.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/true_then_garbage.json b/src/jiffy/test/cases/true_then_garbage.json new file mode 100644 index 0000000..9151612 --- /dev/null +++ b/src/jiffy/test/cases/true_then_garbage.json @@ -0,0 +1 @@ +truex \ No newline at end of file http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/unescaped_bulgarian.eterm ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/unescaped_bulgarian.eterm b/src/jiffy/test/cases/unescaped_bulgarian.eterm new file mode 100644 index 0000000..2a31b61 --- /dev/null +++ b/src/jiffy/test/cases/unescaped_bulgarian.eterm @@ -0,0 +1,5 @@ +[ + <<208, 148, 208, 176, 32, 208, 156, 209, 131, 32, 208, 149, 208, + 177, 208, 176, 32, 208, 156, 208, 176, 208, 185, 208, 186, 208, + 176, 209, 130, 208, 176>> +]. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/cases/unescaped_bulgarian.json ---------------------------------------------------------------------- diff --git a/src/jiffy/test/cases/unescaped_bulgarian.json b/src/jiffy/test/cases/unescaped_bulgarian.json new file mode 100644 index 0000000..f9a70a6 --- /dev/null +++ b/src/jiffy/test/cases/unescaped_bulgarian.json @@ -0,0 +1 @@ +["Ðа ÐÑ Ðба ÐайкаÑа"] http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/etap.erl ---------------------------------------------------------------------- diff --git a/src/jiffy/test/etap.erl b/src/jiffy/test/etap.erl new file mode 100644 index 0000000..6924d09 --- /dev/null +++ b/src/jiffy/test/etap.erl @@ -0,0 +1,612 @@ +%% Copyright (c) 2008-2009 Nick Gerakines <[email protected]> +%% +%% 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. +%% +%% @author Nick Gerakines <[email protected]> [http://socklabs.com/] +%% @author Jeremy Wall <[email protected]> +%% @version 0.3.4 +%% @copyright 2007-2008 Jeremy Wall, 2008-2009 Nick Gerakines +%% @reference http://testanything.org/wiki/index.php/Main_Page +%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol +%% @todo Finish implementing the skip directive. +%% @todo Document the messages handled by this receive loop. +%% @todo Explain in documentation why we use a process to handle test input. +%% @doc etap is a TAP testing module for Erlang components and applications. +%% This module allows developers to test their software using the TAP method. +%% +%% <blockquote cite="http://en.wikipedia.org/wiki/Test_Anything_Protocol"><p> +%% TAP, the Test Anything Protocol, is a simple text-based interface between +%% testing modules in a test harness. TAP started life as part of the test +%% harness for Perl but now has implementations in C/C++, Python, PHP, Perl +%% and probably others by the time you read this. +%% </p></blockquote> +%% +%% The testing process begins by defining a plan using etap:plan/1, running +%% a number of etap tests and then calling eta:end_tests/0. Please refer to +%% the Erlang modules in the t directory of this project for example tests. +-module(etap). +-vsn("0.3.4"). + +-export([ + ensure_test_server/0, + start_etap_server/0, + test_server/1, + msg/1, msg/2, + diag/1, diag/2, + expectation_mismatch_message/3, + plan/1, + end_tests/0, + not_ok/2, ok/2, is_ok/2, is/3, isnt/3, any/3, none/3, + fun_is/3, expect_fun/3, expect_fun/4, + is_greater/3, + skip/1, skip/2, + datetime/1, + skip/3, + bail/0, bail/1, + test_state/0, failure_count/0 +]). + +-export([ + contains_ok/3, + is_before/4 +]). + +-export([ + is_pid/2, + is_alive/2, + is_mfa/3 +]). + +-export([ + loaded_ok/2, + can_ok/2, can_ok/3, + has_attrib/2, is_attrib/3, + is_behaviour/2 +]). + +-export([ + dies_ok/2, + lives_ok/2, + throws_ok/3 +]). + + +-record(test_state, { + planned = 0, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" +}). + +%% @spec plan(N) -> Result +%% N = unknown | skip | {skip, string()} | integer() +%% Result = ok +%% @doc Create a test plan and boot strap the test server. +plan(unknown) -> + ensure_test_server(), + etap_server ! {self(), plan, unknown}, + ok; +plan(skip) -> + io:format("1..0 # skip~n"); +plan({skip, Reason}) -> + io:format("1..0 # skip ~s~n", [Reason]); +plan(N) when is_integer(N), N > 0 -> + ensure_test_server(), + etap_server ! {self(), plan, N}, + ok. + +%% @spec end_tests() -> ok +%% @doc End the current test plan and output test results. +%% @todo This should probably be done in the test_server process. +end_tests() -> + case whereis(etap_server) of + undefined -> self() ! true; + _ -> etap_server ! {self(), state} + end, + State = receive X -> X end, + if + State#test_state.planned == -1 -> + io:format("1..~p~n", [State#test_state.count]); + true -> + ok + end, + case whereis(etap_server) of + undefined -> ok; + _ -> etap_server ! done, ok + end. + +bail() -> + bail(""). + +bail(Reason) -> + etap_server ! {self(), diag, "Bail out! " ++ Reason}, + etap_server ! done, ok, + ok. + +%% @spec test_state() -> Return +%% Return = test_state_record() | {error, string()} +%% @doc Return the current test state +test_state() -> + etap_server ! {self(), state}, + receive + X when is_record(X, test_state) -> X + after + 1000 -> {error, "Timed out waiting for etap server reply.~n"} + end. + +%% @spec failure_count() -> Return +%% Return = integer() | {error, string()} +%% @doc Return the current failure count +failure_count() -> + case test_state() of + #test_state{fail=FailureCount} -> FailureCount; + X -> X + end. + +%% @spec msg(S) -> ok +%% S = string() +%% @doc Print a message in the test output. +msg(S) -> etap_server ! {self(), diag, S}, ok. + +%% @spec msg(Format, Data) -> ok +%% Format = atom() | string() | binary() +%% Data = [term()] +%% UnicodeList = [Unicode] +%% Unicode = int() +%% @doc Print a message in the test output. +%% Function arguments are passed through io_lib:format/2. +msg(Format, Data) -> msg(io_lib:format(Format, Data)). + +%% @spec diag(S) -> ok +%% S = string() +%% @doc Print a debug/status message related to the test suite. +diag(S) -> msg("# " ++ S). + +%% @spec diag(Format, Data) -> ok +%% Format = atom() | string() | binary() +%% Data = [term()] +%% UnicodeList = [Unicode] +%% Unicode = int() +%% @doc Print a debug/status message related to the test suite. +%% Function arguments are passed through io_lib:format/2. +diag(Format, Data) -> diag(io_lib:format(Format, Data)). + +%% @spec expectation_mismatch_message(Got, Expected, Desc) -> ok +%% Got = any() +%% Expected = any() +%% Desc = string() +%% @doc Print an expectation mismatch message in the test output. +expectation_mismatch_message(Got, Expected, Desc) -> + msg(" ---"), + msg(" description: ~p", [Desc]), + msg(" found: ~p", [Got]), + msg(" wanted: ~p", [Expected]), + msg(" ..."), + ok. + +% @spec evaluate(Pass, Got, Expected, Desc) -> Result +%% Pass = true | false +%% Got = any() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Evaluate a test statement, printing an expectation mismatch message +%% if the test failed. +evaluate(Pass, Got, Expected, Desc) -> + case mk_tap(Pass, Desc) of + false -> + expectation_mismatch_message(Got, Expected, Desc), + false; + true -> + true + end. + +%% @spec ok(Expr, Desc) -> Result +%% Expr = true | false +%% Desc = string() +%% Result = true | false +%% @doc Assert that a statement is true. +ok(Expr, Desc) -> evaluate(Expr == true, Expr, true, Desc). + +%% @spec not_ok(Expr, Desc) -> Result +%% Expr = true | false +%% Desc = string() +%% Result = true | false +%% @doc Assert that a statement is false. +not_ok(Expr, Desc) -> evaluate(Expr == false, Expr, false, Desc). + +%% @spec is_ok(Expr, Desc) -> Result +%% Expr = any() +%% Desc = string() +%% Result = true | false +%% @doc Assert that two values are the same. +is_ok(Expr, Desc) -> evaluate(Expr == ok, Expr, ok, Desc). + +%% @spec is(Got, Expected, Desc) -> Result +%% Got = any() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Assert that two values are the same. +is(Got, Expected, Desc) -> evaluate(Got == Expected, Got, Expected, Desc). + +%% @spec isnt(Got, Expected, Desc) -> Result +%% Got = any() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Assert that two values are not the same. +isnt(Got, Expected, Desc) -> evaluate(Got /= Expected, Got, Expected, Desc). + +%% @spec is_greater(ValueA, ValueB, Desc) -> Result +%% ValueA = number() +%% ValueB = number() +%% Desc = string() +%% Result = true | false +%% @doc Assert that an integer is greater than another. +is_greater(ValueA, ValueB, Desc) when is_integer(ValueA), is_integer(ValueB) -> + mk_tap(ValueA > ValueB, Desc). + +%% @spec any(Got, Items, Desc) -> Result +%% Got = any() +%% Items = [any()] +%% Desc = string() +%% Result = true | false +%% @doc Assert that an item is in a list. +any(Got, Items, Desc) when is_function(Got) -> + is(lists:any(Got, Items), true, Desc); +any(Got, Items, Desc) -> + is(lists:member(Got, Items), true, Desc). + +%% @spec none(Got, Items, Desc) -> Result +%% Got = any() +%% Items = [any()] +%% Desc = string() +%% Result = true | false +%% @doc Assert that an item is not in a list. +none(Got, Items, Desc) when is_function(Got) -> + is(lists:any(Got, Items), false, Desc); +none(Got, Items, Desc) -> + is(lists:member(Got, Items), false, Desc). + +%% @spec fun_is(Fun, Expected, Desc) -> Result +%% Fun = function() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Use an anonymous function to assert a pattern match. +fun_is(Fun, Expected, Desc) when is_function(Fun) -> + is(Fun(Expected), true, Desc). + +%% @spec expect_fun(ExpectFun, Got, Desc) -> Result +%% ExpectFun = function() +%% Got = any() +%% Desc = string() +%% Result = true | false +%% @doc Use an anonymous function to assert a pattern match, using actual +%% value as the argument to the function. +expect_fun(ExpectFun, Got, Desc) -> + evaluate(ExpectFun(Got), Got, ExpectFun, Desc). + +%% @spec expect_fun(ExpectFun, Got, Desc, ExpectStr) -> Result +%% ExpectFun = function() +%% Got = any() +%% Desc = string() +%% ExpectStr = string() +%% Result = true | false +%% @doc Use an anonymous function to assert a pattern match, using actual +%% value as the argument to the function. +expect_fun(ExpectFun, Got, Desc, ExpectStr) -> + evaluate(ExpectFun(Got), Got, ExpectStr, Desc). + +%% @equiv skip(TestFun, "") +skip(TestFun) when is_function(TestFun) -> + skip(TestFun, ""). + +%% @spec skip(TestFun, Reason) -> ok +%% TestFun = function() +%% Reason = string() +%% @doc Skip a test. +skip(TestFun, Reason) when is_function(TestFun), is_list(Reason) -> + begin_skip(Reason), + catch TestFun(), + end_skip(), + ok. + +%% @spec skip(Q, TestFun, Reason) -> ok +%% Q = true | false | function() +%% TestFun = function() +%% Reason = string() +%% @doc Skips a test conditionally. The first argument to this function can +%% either be the 'true' or 'false' atoms or a function that returns 'true' or +%% 'false'. +skip(QFun, TestFun, Reason) when is_function(QFun), is_function(TestFun), is_list(Reason) -> + case QFun() of + true -> begin_skip(Reason), TestFun(), end_skip(); + _ -> TestFun() + end, + ok; + +skip(Q, TestFun, Reason) when is_function(TestFun), is_list(Reason), Q == true -> + begin_skip(Reason), + TestFun(), + end_skip(), + ok; + +skip(_, TestFun, Reason) when is_function(TestFun), is_list(Reason) -> + TestFun(), + ok. + +%% @private +begin_skip(Reason) -> + etap_server ! {self(), begin_skip, Reason}. + +%% @private +end_skip() -> + etap_server ! {self(), end_skip}. + +%% @spec contains_ok(string(), string(), string()) -> true | false +%% @doc Assert that a string is contained in another string. +contains_ok(Source, String, Desc) -> + etap:isnt( + string:str(Source, String), + 0, + Desc + ). + +%% @spec is_before(string(), string(), string(), string()) -> true | false +%% @doc Assert that a string comes before another string within a larger body. +is_before(Source, StringA, StringB, Desc) -> + etap:is_greater( + string:str(Source, StringB), + string:str(Source, StringA), + Desc + ). + +%% @doc Assert that a given variable is a pid. +is_pid(Pid, Desc) when is_pid(Pid) -> etap:ok(true, Desc); +is_pid(_, Desc) -> etap:ok(false, Desc). + +%% @doc Assert that a given process/pid is alive. +is_alive(Pid, Desc) -> + etap:ok(erlang:is_process_alive(Pid), Desc). + +%% @doc Assert that the current function of a pid is a given {M, F, A} tuple. +is_mfa(Pid, MFA, Desc) -> + etap:is({current_function, MFA}, erlang:process_info(Pid, current_function), Desc). + +%% @spec loaded_ok(atom(), string()) -> true | false +%% @doc Assert that a module has been loaded successfully. +loaded_ok(M, Desc) when is_atom(M) -> + etap:fun_is(fun({module, _}) -> true; (_) -> false end, code:load_file(M), Desc). + +%% @spec can_ok(atom(), atom()) -> true | false +%% @doc Assert that a module exports a given function. +can_ok(M, F) when is_atom(M), is_atom(F) -> + Matches = [X || {X, _} <- M:module_info(exports), X == F], + etap:ok(Matches > 0, lists:concat([M, " can ", F])). + +%% @spec can_ok(atom(), atom(), integer()) -> true | false +%% @doc Assert that a module exports a given function with a given arity. +can_ok(M, F, A) when is_atom(M); is_atom(F), is_number(A) -> + Matches = [X || X <- M:module_info(exports), X == {F, A}], + etap:ok(Matches > 0, lists:concat([M, " can ", F, "/", A])). + +%% @spec has_attrib(M, A) -> true | false +%% M = atom() +%% A = atom() +%% @doc Asserts that a module has a given attribute. +has_attrib(M, A) when is_atom(M), is_atom(A) -> + etap:isnt( + proplists:get_value(A, M:module_info(attributes), 'asdlkjasdlkads'), + 'asdlkjasdlkads', + lists:concat([M, " has attribute ", A]) + ). + +%% @spec has_attrib(M, A. V) -> true | false +%% M = atom() +%% A = atom() +%% V = any() +%% @doc Asserts that a module has a given attribute with a given value. +is_attrib(M, A, V) when is_atom(M) andalso is_atom(A) -> + etap:is( + proplists:get_value(A, M:module_info(attributes)), + [V], + lists:concat([M, "'s ", A, " is ", V]) + ). + +%% @spec is_behavior(M, B) -> true | false +%% M = atom() +%% B = atom() +%% @doc Asserts that a given module has a specific behavior. +is_behaviour(M, B) when is_atom(M) andalso is_atom(B) -> + is_attrib(M, behaviour, B). + +%% @doc Assert that an exception is raised when running a given function. +dies_ok(F, Desc) -> + case (catch F()) of + {'EXIT', _} -> etap:ok(true, Desc); + _ -> etap:ok(false, Desc) + end. + +%% @doc Assert that an exception is not raised when running a given function. +lives_ok(F, Desc) -> + etap:is(try_this(F), success, Desc). + +%% @doc Assert that the exception thrown by a function matches the given exception. +throws_ok(F, Exception, Desc) -> + try F() of + _ -> etap:ok(nok, Desc) + catch + _:E -> + etap:is(E, Exception, Desc) + end. + +%% @private +%% @doc Run a function and catch any exceptions. +try_this(F) when is_function(F, 0) -> + try F() of + _ -> success + catch + throw:E -> {throw, E}; + error:E -> {error, E}; + exit:E -> {exit, E} + end. + +%% @private +%% @doc Start the etap_server process if it is not running already. +ensure_test_server() -> + case whereis(etap_server) of + undefined -> + proc_lib:start(?MODULE, start_etap_server,[]); + _ -> + diag("The test server is already running.") + end. + +%% @private +%% @doc Start the etap_server loop and register itself as the etap_server +%% process. +start_etap_server() -> + catch register(etap_server, self()), + proc_lib:init_ack(ok), + etap:test_server(#test_state{ + planned = 0, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" + }). + + +%% @private +%% @doc The main etap_server receive/run loop. The etap_server receive loop +%% responds to seven messages apperatining to failure or passing of tests. +%% It is also used to initiate the testing process with the {_, plan, _} +%% message that clears the current test state. +test_server(State) -> + NewState = receive + {_From, plan, unknown} -> + io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), + io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), + State#test_state{ + planned = -1, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" + }; + {_From, plan, N} -> + io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), + io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), + io:format("1..~p~n", [N]), + State#test_state{ + planned = N, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" + }; + {_From, begin_skip, Reason} -> + State#test_state{ + skip = 1, + skip_reason = Reason + }; + {_From, end_skip} -> + State#test_state{ + skip = 0, + skip_reason = "" + }; + {_From, pass, Desc} -> + FullMessage = skip_diag( + " - " ++ Desc, + State#test_state.skip, + State#test_state.skip_reason + ), + io:format("ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), + State#test_state{ + count = State#test_state.count + 1, + pass = State#test_state.pass + 1 + }; + + {_From, fail, Desc} -> + FullMessage = skip_diag( + " - " ++ Desc, + State#test_state.skip, + State#test_state.skip_reason + ), + io:format("not ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), + State#test_state{ + count = State#test_state.count + 1, + fail = State#test_state.fail + 1 + }; + {From, state} -> + From ! State, + State; + {_From, diag, Message} -> + io:format("~s~n", [Message]), + State; + {From, count} -> + From ! State#test_state.count, + State; + {From, is_skip} -> + From ! State#test_state.skip, + State; + done -> + exit(normal) + end, + test_server(NewState). + +%% @private +%% @doc Process the result of a test and send it to the etap_server process. +mk_tap(Result, Desc) -> + IsSkip = lib:sendw(etap_server, is_skip), + case [IsSkip, Result] of + [_, true] -> + etap_server ! {self(), pass, Desc}, + true; + [1, _] -> + etap_server ! {self(), pass, Desc}, + true; + _ -> + etap_server ! {self(), fail, Desc}, + false + end. + +%% @private +%% @doc Format a date/time string. +datetime(DateTime) -> + {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, + io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B", [Year, Month, Day, Hour, Min, Sec]). + +%% @private +%% @doc Craft an output message taking skip/todo into consideration. +skip_diag(Message, 0, _) -> + Message; +skip_diag(_Message, 1, "") -> + " # SKIP"; +skip_diag(_Message, 1, Reason) -> + " # SKIP : " ++ Reason. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/jiffy_tests.erl ---------------------------------------------------------------------- diff --git a/src/jiffy/test/jiffy_tests.erl b/src/jiffy/test/jiffy_tests.erl new file mode 100644 index 0000000..0655b0d --- /dev/null +++ b/src/jiffy/test/jiffy_tests.erl @@ -0,0 +1,135 @@ +% This file is part of Jiffy released under the MIT license. +% See the LICENSE file for more information. + +-module(jiffy_tests). + +-ifdef(JIFFY_DEV). + +-include_lib("proper/include/proper.hrl"). +-include_lib("eunit/include/eunit.hrl"). + + +proper_test_() -> + PropErOpts = [ + {to_file, user}, + {max_size, 15}, + {numtests, 1000} + ], + {timeout, 3600, ?_assertEqual([], proper:module(jiffy_tests, PropErOpts))}. + + +prop_encode_decode() -> + ?FORALL(Data, json(), + begin + %io:format(standard_error, "Data: ~p~n", [Data]), + Data == jiffy:decode(jiffy:encode(Data)) + end + ). + +prop_encode_decode_pretty() -> + ?FORALL(Data, json(), + begin + Data == jiffy:decode(jiffy:encode(Data, [pretty])) + end + ). + +prop_encode_not_crash() -> + ?FORALL(Data, any(), begin catch jiffy:encode(Data), true end). + +prop_decode_not_crash_bin() -> + ?FORALL(Data, binary(), begin catch jiffy:decode(Data), true end). + +prop_decode_not_crash_any() -> + ?FORALL(Data, any(), begin catch jiffy:decode(Data), true end). + + +% JSON Generation + + +json_null() -> + null. + + +json_boolean() -> + oneof([true, false]). + + +json_number() -> + oneof([integer(), float()]). + + +json_string() -> + escaped_utf8_bin(). + + +json_list(S) when S =< 0 -> + []; +json_list(S) -> + ?LETSHRINK( + [ListSize], + [integer(0, S)], + vector(ListSize, json_text(S - ListSize)) + ). + + +json_object(S) when S =< 0 -> + {[]}; +json_object(S) -> + ?LETSHRINK( + [ObjectSize], + [integer(0, S)], + {vector(ObjectSize, {json_string(), json_text(S - ObjectSize)})} + ). + + +json_value() -> + oneof([ + json_null(), + json_boolean(), + json_string(), + json_number() + ]). + + +json_text(S) when S > 0 -> + ?LAZY(oneof([ + json_list(S), + json_object(S) + ])); +json_text(_) -> + json_value(). + + +json() -> + ?SIZED(S, json_text(S)). + + +%% XXX: Add generators +% +% We should add generators that generate JSON binaries directly +% so we can test things that aren't produced by the encoder. +% +% We should also have a version of the JSON generator that inserts +% errors into the JSON that we can test for. + + +escaped_utf8_bin() -> + ?SUCHTHAT(Bin, + ?LET(S, ?SUCHTHAT(L, list(escaped_char()), L /= []), + unicode:characters_to_binary(S, unicode, utf8)), + is_binary(Bin) + ). + + +escaped_char() -> + ?LET(C, char(), + case C of + $" -> "\\\""; + C when C == 65534 -> 65533; + C when C == 65535 -> 65533; + C when C > 1114111 -> 1114111; + C -> C + end + ). + +-endif. http://git-wip-us.apache.org/repos/asf/couchdb/blob/2e6092e4/src/jiffy/test/util.erl ---------------------------------------------------------------------- diff --git a/src/jiffy/test/util.erl b/src/jiffy/test/util.erl new file mode 100644 index 0000000..ceaef89 --- /dev/null +++ b/src/jiffy/test/util.erl @@ -0,0 +1,44 @@ +-module(util). +-export([test_good/1, test_good/2, test_errors/1]). + +test_good(Cases) -> + test_good(Cases, []). + +test_good(Cases, Options) -> + lists:foreach(fun(Case) -> check_good(Case, Options) end, Cases). + +test_errors(Cases) -> + lists:foreach(fun(Case) -> check_error(Case) end, Cases). + +ok_dec(J, _E) -> + lists:flatten(io_lib:format("Decoded ~p.", [J])). + +ok_enc(E, _J) -> + lists:flatten(io_lib:format("Encoded ~p", [E])). + +do_encode(E, Options) -> + iolist_to_binary(jiffy:encode(E, Options)). + +error_mesg(J) -> + lists:flatten(io_lib:format("Decoding ~p returns an error.", [J])). + +check_good({J, E}, Options) -> + etap:is(jiffy:decode(J), E, ok_dec(J, E)), + etap:is(do_encode(E, Options), J, ok_enc(E, J)); +check_good({J, E, J2}, Options) -> + etap:is(jiffy:decode(J), E, ok_dec(J, E)), + etap:is(do_encode(E, Options), J2, ok_enc(E, J2)). + +check_error({J, E}) -> + etap:fun_is( + fun({error, E1}) when E1 == E -> true; (E1) -> E1 end, + (catch jiffy:decode(J)), + error_mesg(J) + ); +check_error(J) -> + etap:fun_is( + fun({error, _}) -> true; (Else) -> Else end, + (catch jiffy:decode(J)), + error_mesg(J) + ). +
