Repository: couchdb-khash Updated Branches: refs/heads/master f8a77ddb1 -> 6938d72be
Migrate the tests from etap to eunit All the etap tests converted to eunit. COUCHDB-2590 Project: http://git-wip-us.apache.org/repos/asf/couchdb-khash/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-khash/commit/6938d72b Tree: http://git-wip-us.apache.org/repos/asf/couchdb-khash/tree/6938d72b Diff: http://git-wip-us.apache.org/repos/asf/couchdb-khash/diff/6938d72b Branch: refs/heads/master Commit: 6938d72be999c4fb876aa890c1c60105216dc51b Parents: f8a77dd Author: Eric Avdey <e...@eiri.ca> Authored: Mon May 4 15:38:27 2015 -0300 Committer: Eric Avdey <e...@eiri.ca> Committed: Thu Jul 16 16:56:40 2015 -0300 ---------------------------------------------------------------------- .gitignore | 2 + Makefile | 8 +- test/001-load.t | 19 -- test/002-basics.t | 29 -- test/003-randomized.t | 147 ---------- test/004-race-dict-fetch.t | 59 ---- test/005-race-dict-store.t | 56 ---- test/006-iterators.t | 115 -------- test/etap.erl | 612 ---------------------------------------- test/khash_test.erl | 441 +++++++++++++++++++++++++++++ test/util.erl | 25 -- 11 files changed, 447 insertions(+), 1066 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/.gitignore ---------------------------------------------------------------------- diff --git a/.gitignore b/.gitignore index e57b0dc..ff360df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ c_src/*.o ebin/ test/*.beam +.eunit +.rebar priv/*.so http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/Makefile ---------------------------------------------------------------------- diff --git a/Makefile b/Makefile index 5ecb125..73f4484 100644 --- a/Makefile +++ b/Makefile @@ -18,15 +18,15 @@ build: $(REBAR) compile -etap: test/etap.beam test/util.beam test/gen_term.beam - prove test/*.t +eunit: + $(REBAR) eunit -check: build etap +check: build eunit %.beam: %.erl erlc -o test/ $< -.PHONY: all clean distclean build etap check +.PHONY: all clean distclean build eunit check http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/001-load.t ---------------------------------------------------------------------- diff --git a/test/001-load.t b/test/001-load.t deleted file mode 100755 index 27b3c07..0000000 --- a/test/001-load.t +++ /dev/null @@ -1,19 +0,0 @@ -#! /usr/bin/env escript -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - -main([]) -> - code:add_pathz("test"), - code:add_pathz("ebin"), - - Modules = [ - khash - ], - - etap:plan(length(Modules)), - lists:foreach(fun(M) -> - etap:loaded_ok(M, "Loaded " ++ atom_to_list(M)) - end, Modules), - etap:end_tests(). - http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/002-basics.t ---------------------------------------------------------------------- diff --git a/test/002-basics.t b/test/002-basics.t deleted file mode 100755 index 058b820..0000000 --- a/test/002-basics.t +++ /dev/null @@ -1,29 +0,0 @@ -#! /usr/bin/env escript -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - --mode(compile). - -main([]) -> - code:add_pathz("test"), - util:run(12, fun() -> - test(), - ok - end). - - -test() -> - {ok, C} = khash:new(), - etap:is(khash:lookup(C, <<"foo">>), not_found, "Lookup missing is ok"), - etap:is(khash:get(C, <<"foo">>), undefined, "Get missing is ok"), - etap:is(khash:del(C, <<"foo">>), not_found, "Del missing is ok"), - etap:is(khash:put(C, <<"foo">>, bar), ok, "Stored a key"), - etap:is(khash:lookup(C, <<"foo">>), {value, bar}, "Lookuped a key"), - etap:is(khash:get(C, <<"foo">>), bar, "Retrieved a key"), - etap:is(khash:put(C, <<"bar">>, foo), ok, "Stored a key"), - etap:is(khash:size(C), 2, "Correct size for hash"), - etap:is(khash:del(C, <<"foo">>), ok, "Deleted a key"), - etap:is(khash:size(C), 1, "Correct size after delete"), - etap:is(khash:clear(C), ok, "Cleared the hash"), - etap:is(khash:size(C), 0, "Correct size after clear"). http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/003-randomized.t ---------------------------------------------------------------------- diff --git a/test/003-randomized.t b/test/003-randomized.t deleted file mode 100755 index 5695ce8..0000000 --- a/test/003-randomized.t +++ /dev/null @@ -1,147 +0,0 @@ -#! /usr/bin/env escript -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - --mode(compile). - -num_cycles() -> 10000. - -main([]) -> - code:add_pathz("test"), - random:seed(erlang:now()), - - util:run(num_cycles(), fun() -> - test_random(), - ok - end). - - -test_random() -> - D = dict:new(), - {ok, H} = khash:new(), - - Actions = [ - {0.1, fun(S) -> run_clear(S) end}, - {1.0, fun(S) -> run_get2(S) end}, - {1.0, fun(S) -> run_get3(S) end}, - {1.0, fun(S) -> run_put(S) end}, - {1.0, fun(S) -> run_del(S) end}, - {0.5, fun(S) -> run_size(S) end}, - %{0.3, fun(S) -> run_keys(S) end}, - {0.3, fun(S) -> run_to_list(S) end} - ], - - run(Actions, num_cycles(), {D, H}). - - -run(_, N, _S) when N =< 0 -> - ok; -run(Actions, N, S0) -> - Action = weighted_choice(Actions), - S1 = Action(S0), - true = check_state(S1), - run(Actions, N-1, S1). - - -run_clear({_D0, H}) -> - ok = khash:clear(H), - {dict:new(), H}. - - -run_get2({D, H}) -> - K = random_key(D), - case dict:find(K, D) of - {ok, Value} -> - {value, Value} = khash:lookup(H, K), - Value = khash:get(H, K); - error -> - not_found = khash:lookup(H, K), - undefined = khash:get(H, K) - end, - {D, H}. - - -run_get3({D, H}) -> - K = random_key(D), - case dict:find(K, D) of - {ok, Value} -> - {value, Value} = khash:lookup(H, K), - Value = khash:get(H, K); - error -> - Val = random_val(), - Val = khash:get(H, K, Val) - end, - {D, H}. - - -run_put({D0, H}) -> - K = random_key(D0), - V = random_val(), - D1 = dict:store(K, V, D0), - ok = khash:put(H, K, V), - {D1, H}. - - -run_del({D0, H}) -> - K = random_key(D0), - D1 = case dict:is_key(K, D0) of - true -> - ok = khash:del(H, K), - dict:erase(K, D0); - false -> - not_found = khash:del(H, K), - D0 - end, - {D1, H}. - - -run_size({D, H}) -> - S = dict:size(D), - S = khash:size(H), - {D, H}. - - -%run_keys({D, H}) -> -% DKeys = lists:sort(dict:fetch_keys(D)), -% {ok, HKeys0} = khash:keys(H), -% HKeys = lists:sort(HKeys0), -% DKeys = HKeys, -% {D, H}. - - -run_to_list({D, H}) -> - DKVs = lists:sort(dict:to_list(D)), - HKVs = lists:sort(khash:to_list(H)), - DKVs = HKVs, - {D, H}. - - -check_state({D, H}) -> - DKVs = lists:sort(dict:to_list(D)), - HKVs = lists:sort(khash:to_list(H)), - etap:is(DKVs, HKVs, "State matches dict implementation"). - - -weighted_choice(Items0) -> - Items = lists:sort(Items0), - Sum = lists:sum([W || {W, _} <- Items]), - Choice = random:uniform() * Sum, - weighted_choice(Items, 0.0, Choice). - - -weighted_choice([], _, _) -> - throw(bad_choice); -weighted_choice([{W, _} | Rest], S, C) when W + S < C -> - weighted_choice(Rest, W+S, C); -weighted_choice([{_, I} | _], _, _) -> - I. - - -random_key(D) -> - Keys = lists:usort(dict:fetch_keys(D) ++ [foo]), - lists:nth(random:uniform(length(Keys)), Keys). - - -random_val() -> - gen_term:any(). http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/004-race-dict-fetch.t ---------------------------------------------------------------------- diff --git a/test/004-race-dict-fetch.t b/test/004-race-dict-fetch.t deleted file mode 100755 index 7102a9f..0000000 --- a/test/004-race-dict-fetch.t +++ /dev/null @@ -1,59 +0,0 @@ -#! /usr/bin/env escript -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - --mode(compile). - -num_cycles() -> 10000000. -num_kvs() -> 5000. - - -main([]) -> - % Let the VM settle for a bit - receive after 1000 -> ok end, - - code:add_pathz("test"), - util:run(1, fun() -> - test(), - ok - end). - - -test() -> - {DTime, _} = timer:tc(fun() -> test_dict() end, []), - {KTime, _} = timer:tc(fun() -> test_khash() end, []), - etap:diag("Dict: ~10b", [DTime]), - etap:diag("KHash: ~10b", [KTime]), - etap:is_greater(DTime, KTime, "Dict is slower than khash"). - - -test_dict() -> - erlang:garbage_collect(), - D = dict:from_list(kv_data()), - test_dict(D, num_cycles()). - - -test_dict(_D, 0) -> - ok; -test_dict(D, NumCycles) -> - bing = dict:fetch(1, D), - test_dict(D, NumCycles - 1). - - -test_khash() -> - erlang:garbage_collect(), - {ok, H} = khash:from_list(kv_data()), - test_khash(H, num_cycles()). - - -test_khash(_H, 0) -> - ok; -test_khash(H, NumCycles) -> - bing = khash:get(H, 1), - test_khash(H, NumCycles - 1). - - -kv_data() -> - [{I, bing} || I <- lists:seq(1, num_kvs())]. - http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/005-race-dict-store.t ---------------------------------------------------------------------- diff --git a/test/005-race-dict-store.t b/test/005-race-dict-store.t deleted file mode 100755 index de73c62..0000000 --- a/test/005-race-dict-store.t +++ /dev/null @@ -1,56 +0,0 @@ -#! /usr/bin/env escript -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - --mode(compile). - -num_cycles() -> 1000000. -num_kvs() -> 10000. - -main([]) -> - % Let the VM settle for a bit - receive after 1000 -> ok end, - - code:add_pathz("test"), - util:run(1, fun() -> - test(), - ok - end). - - -test() -> - {DTime, _} = timer:tc(fun() -> test_dict() end, []), - {KTime, _} = timer:tc(fun() -> test_khash() end, []), - etap:diag("Dict: ~10b", [DTime]), - etap:diag("KHash: ~10b", [KTime]), - etap:is_greater(DTime, KTime, "Dict is slower than khash"). - - -test_dict() -> - D = dict:from_list(kv_data()), - test_dict(D, num_cycles()). - - -test_dict(_D, 0) -> - ok; -test_dict(D, NumCycles) -> - D2 = dict:store(1, bing, D), - test_dict(D2, NumCycles - 1). - - -test_khash() -> - {ok, H} = khash:from_list(kv_data()), - test_khash(H, num_cycles()). - - -test_khash(_H, 0) -> - ok; -test_khash(H, NumCycles) -> - khash:put(H, 1, bing), - test_khash(H, NumCycles - 1). - - -kv_data() -> - [{I, bing} || I <- lists:seq(1, num_kvs())]. - http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/006-iterators.t ---------------------------------------------------------------------- diff --git a/test/006-iterators.t b/test/006-iterators.t deleted file mode 100755 index 02a7478..0000000 --- a/test/006-iterators.t +++ /dev/null @@ -1,115 +0,0 @@ -#! /usr/bin/env escript -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - --mode(compile). - -main([]) -> - code:add_pathz("test"), - util:run(12, fun() -> - test(), - ok - end). - - -test() -> - test_basic(), - test_multi(), - test_expiration(), - test_no_expiration(), - ok. - - -test_basic() -> - {ok, H} = khash:new(), - khash:put(H, foo, bar), - {ok, I} = khash:iter(H), - etap:is(khash:iter_next(I), {foo,bar}, "Got only kv pair as first element"), - etap:is(khash:iter_next(I), end_of_table, "Only the one kv pair exists"), - FoldFun = fun(K, V, Acc) -> [{K,V} | Acc] end, - etap:is(khash:fold(H, FoldFun, []), [{foo,bar}], "fold works"). - - -test_multi() -> - {ok, H} = khash:new(), - KVs = [{I, I} || I <- lists:seq(1, 10)], - lists:foreach(fun({K,V}) -> khash:put(H, K, V) end, KVs), - {ok, I} = khash:iter(H), - ReadKVs = test_multi_read(I, []), - etap:is(ReadKVs, KVs, "Read the same exact key/val pairs"). - - -test_multi_read(Iter, KVs) -> - case khash:iter_next(Iter) of - {K, V} -> - test_multi_read(Iter, [{K,V} | KVs]); - end_of_table -> - lists:sort(KVs) - end. - - -test_expiration() -> - test_expiration("put", fun(H) -> - khash:put(H, foo, bar2), - ok - end), - - test_expiration("del", fun(H) -> - khash:del(H, foo), - ok - end), - - test_expiration("clear", fun(H) -> - khash:clear(H), - ok - end). - - -test_expiration(FunName, Fun) -> - Error = {error, expired_iterator}, - {ok, H} = khash:new(), - khash:put(H, foo, bar), - {ok, I} = khash:iter(H), - ok = Fun(H), - Msg = FunName ++ " expires iterators", - etap:is(khash:iter_next(I), Error, Msg). - - -test_no_expiration() -> - test_no_expiration("to_list", fun(H) -> - [{foo,bar}] = khash:to_list(H), - ok - end), - - test_no_expiration("lookup", fun(H) -> - {value,bar} = khash:lookup(H,foo), - ok - end), - - test_no_expiration("get", fun(H) -> - bar = khash:get(H, foo), - ok - end), - - test_no_expiration("size", fun(H) -> - 1 = khash:size(H), - ok - end), - - test_no_expiration("iteration", fun(H) -> - {ok, I} = khash:iter(H), - {foo, bar} = khash:iter_next(I), - end_of_table = khash:iter_next(I), - ok - end). - - -test_no_expiration(FunName, Fun) -> - Error = {error, expired_iterator}, - {ok, H} = khash:new(), - khash:put(H, foo, bar), - {ok, I} = khash:iter(H), - ok = Fun(H), - Msg = FunName ++ " doesn't expire iterators", - etap:isnt(khash:iter_next(I), Error, Msg). http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/etap.erl ---------------------------------------------------------------------- diff --git a/test/etap.erl b/test/etap.erl deleted file mode 100644 index 6924d09..0000000 --- a/test/etap.erl +++ /dev/null @@ -1,612 +0,0 @@ -%% Copyright (c) 2008-2009 Nick Gerakines <n...@gerakines.net> -%% -%% 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 <n...@gerakines.net> [http://socklabs.com/] -%% @author Jeremy Wall <jer...@marzhillstudios.com> -%% @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-khash/blob/6938d72b/test/khash_test.erl ---------------------------------------------------------------------- diff --git a/test/khash_test.erl b/test/khash_test.erl new file mode 100644 index 0000000..cab9820 --- /dev/null +++ b/test/khash_test.erl @@ -0,0 +1,441 @@ +-module(khash_test). + +-compile([export_all]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(NUM_RAND_CYCLES, 10000). +-define(NUM_CYCLES, 1000000). +-define(NUM_KVS, 5000). + +load_test_() -> + { + "Loaded khash", + ?_assertEqual({module, khash}, code:load_file(khash)) + }. + +basic_test_() -> + { + "khash basic operations", + {setup, + local, + fun() -> khash:new() end, + fun({ok, _}) -> ok end, + fun({ok, C}) -> + [ + { + "Lookup missing is ok", + ?_assertEqual(not_found, khash:lookup(C, <<"foo">>)) + }, + { + "Get missing is ok", + ?_assertEqual(undefined, khash:get(C, <<"foo">>)) + }, + { + "Del missing is ok", + ?_assertEqual(not_found, khash:del(C, <<"foo">>)) + }, + { + "Stored a key", + ?_assertEqual(ok, khash:put(C, <<"foo">>, bar)) + }, + { + "Lookuped a key", + ?_assertEqual({value, bar}, khash:lookup(C, <<"foo">>)) + }, + { + "Retrieved a key", + ?_assertEqual(bar, khash:get(C, <<"foo">>)) + }, + { + "Stored a key", + ?_assertEqual(ok, khash:put(C, <<"bar">>, foo)) + }, + { + "Correct size for hash", + ?_assertEqual(2, khash:size(C)) + }, + { + "Deleted a key", + ?_assertEqual(ok, khash:del(C, <<"foo">>)) + }, + { + "Correct size after delete", + ?_assertEqual(1, khash:size(C)) + }, + { + "Cleared the hash", + ?_assertEqual(ok, khash:clear(C)) + }, + { + "Correct size after clear", + ?_assertEqual(0, khash:size(C)) + } + ] + end + } + }. + +randomized_test_() -> + { + "khash randomized test", + {setup, + local, + fun() -> + random:seed(erlang:now()), + Dict = dict:new(), + {ok, KHash} = khash:new(), + Actions = [ + {0.1, fun run_clear/1}, + {1.0, fun run_get2/1}, + {1.0, fun run_get3/1}, + {1.0, fun run_put/1}, + {1.0, fun run_del/1}, + {0.5, fun run_size/1}, + % {0.3, fun run_keys/1}, + {0.3, fun run_to_list/1} + ], + {ok, Actions, ?NUM_RAND_CYCLES, {Dict, KHash}} + end, + fun(State) -> + { + "State matches dict implementation", + ?_assert(run_randomized(State, true)) + } + end + } + }. + +compare_dict_test_() -> + { + "khash vs dict", + {setup, + fun() -> + % Let the VM settle for a bit + receive after 1000 -> ok end + end, + fun(ok) -> + [{ + "Dict's fetch is slower than of khash", + ?_test(begin + {DTime, _} = timer:tc(fun() -> dict_fetch() end, []), + {KTime, _} = timer:tc(fun() -> khash_fetch() end, []), + ?debugFmt("Dict: ~10b", [DTime]), + ?debugFmt("KHash: ~10b", [KTime]), + ?assert(DTime > KTime) + end) + }, + { + "Dict's store is slower than of khash", + ?_test(begin + {DTime, _} = timer:tc(fun() -> dict_store() end, []), + {KTime, _} = timer:tc(fun() -> khash_store() end, []), + ?debugFmt("Dict: ~10b", [DTime]), + ?debugFmt("KHash: ~10b", [KTime]), + ?assert(DTime > KTime) + end) + }] + end + } + }. + +basic_iterators_test_() -> + { + "khash itrators basics operations", + {setup, + local, + fun() -> + {ok, H} = khash:new(), + khash:put(H, foo, bar), + {ok, I} = khash:iter(H), + {ok, H, I} + end, + fun({ok, H, I}) -> + [ + { + "Got only kv pair as first element", + ?_assertEqual(khash:iter_next(I), {foo,bar}) + }, + { + "Only the one kv pair exists", + ?_assertEqual(khash:iter_next(I), end_of_table) + }, + { + "Fold works", + ?_test(begin + Fold = fun(K, V, Acc) -> [{K,V} | Acc] end, + ?assertEqual(khash:fold(H, Fold, []), [{foo,bar}]) + end) + } + ] + end + } + }. + +multiread_iterators_test_() -> + { + "khash iterators multi-read test", + {setup, + local, + fun() -> + {ok, H} = khash:new(), + KVs = kv_data(10), + lists:foreach(fun({K,V}) -> khash:put(H, K, V) end, KVs), + {ok, I} = khash:iter(H), + {ok, I, KVs} + end, + fun({ok, I, KVs}) -> + { + "Read the same exact key/val pairs", + ?_assertEqual(khash_iterate(I, []), KVs) + } + end + } + }. + +expiration_iterators_test_() -> + { + "khash iterators exipiration functions", + {foreach, + local, + fun() -> + Err = {error, expired_iterator}, + {ok, H} = khash:new(), + khash:put(H, foo, bar), + {ok, I} = khash:iter(H), + {ok, H, I, Err} + end, + [ + fun({ok, H, I, Err}) -> + khash:put(H, foo, bar2), + { + "put expires iterators", + ?_assertEqual(khash:iter_next(I), Err) + } + end, + fun({ok, H, I, Err}) -> + khash:del(H, foo), + { + "del expires iterators", + ?_assertEqual(khash:iter_next(I), Err) + } + end, + fun({ok, H, I, Err}) -> + khash:clear(H), + { + "clear expires iterators", + ?_assertEqual(khash:iter_next(I), Err) + } + end + ] + } + }. + +no_expiration_iterators_test_() -> + { + "khash iterators no exipiration functions", + {foreach, + local, + fun() -> + Err = {error, expired_iterator}, + {ok, H} = khash:new(), + khash:put(H, foo, bar), + {ok, I} = khash:iter(H), + {ok, H, I, Err} + end, + [ + fun({ok, H, I, Err}) -> + [{foo, bar}] = khash:to_list(H), + { + "to_list doesn't expire iterators", + ?_assertNot(khash:iter_next(I) == Err) + } + end, + fun({ok, H, I, Err}) -> + {value, bar} = khash:lookup(H,foo), + { + "lookup doesn't expire iterators", + ?_assertNot(khash:iter_next(I) == Err) + } + end, + fun({ok, H, I, Err}) -> + bar = khash:get(H, foo), + { + "get doesn't expire iterators", + ?_assertNot(khash:iter_next(I) == Err) + } + end, + fun({ok, H, I, Err}) -> + 1 = khash:size(H), + { + "size doesn't expire iterators", + ?_assertNot(khash:iter_next(I) == Err) + } + end, + fun({ok, H, I, Err}) -> + {ok, I} = khash:iter(H), + {foo, bar} = khash:iter_next(I), + end_of_table = khash:iter_next(I), + { + "iteration doesn't expire iterators", + ?_assertNot(khash:iter_next(I) == Err) + } + end + ] + } + }. + + +dict_fetch() -> + erlang:garbage_collect(), + List = kv_data(?NUM_KVS), + Dict = dict:from_list(List), + dict_fetch(Dict, ?NUM_CYCLES * 10). + +dict_fetch(_, 0) -> + ok; +dict_fetch(Dict, NumCycles) -> + ?assertMatch(1, dict:fetch(1, Dict)), + dict_fetch(Dict, NumCycles - 1). + +khash_fetch() -> + erlang:garbage_collect(), + List = kv_data(?NUM_KVS), + {ok, KHash} = khash:from_list(List), + khash_fetch(KHash, ?NUM_CYCLES * 10). + +khash_fetch(_, 0) -> + ok; +khash_fetch(KHash, NumCycles) -> + ?assertMatch(1, khash:get(KHash, 1)), + khash_fetch(KHash, NumCycles - 1). + +dict_store() -> + List = kv_data(?NUM_KVS * 2), + Dict = dict:from_list(List), + dict_store(Dict, ?NUM_CYCLES). + +dict_store(_, 0) -> + ok; +dict_store(Dict, NumCycles) -> + Dict2 = dict:store(1, 1, Dict), + dict_store(Dict2, NumCycles - 1). + +khash_store() -> + List = kv_data(?NUM_KVS * 2), + {ok, KHash} = khash:from_list(List), + khash_store(KHash, ?NUM_CYCLES). + +khash_store(_, 0) -> + ok; +khash_store(KHash, NumCycles) -> + khash:put(KHash, 1, 1), + khash_store(KHash, NumCycles - 1). + +khash_iterate(I, Acc) -> + case khash:iter_next(I) of + {K, V} -> + khash_iterate(I, [{K, V}|Acc]); + end_of_table -> + lists:sort(Acc) + end. + +kv_data(NumKVs) -> + [{I, I} || I <- lists:seq(1, NumKVs)]. + +run_randomized({ok, _, N, _}, Valid) when N =< 0 -> + Valid; +run_randomized({ok, Actions, N, S0}, Valid) -> + Action = weighted_choice(Actions), + S1 = Action(S0), + Assertion = Valid andalso validate_randomized_state(S1), + run_randomized({ok, Actions, N - 1, S1}, Assertion). + +validate_randomized_state({D, H}) -> + DKVs = lists:sort(dict:to_list(D)), + HKVs = lists:sort(khash:to_list(H)), + DKVs == HKVs. + +run_clear({_D0, H}) -> + ?assertMatch(ok, khash:clear(H)), + {dict:new(), H}. + +run_get2({D, H}) -> + K = random_key(D), + case dict:find(K, D) of + {ok, Value} -> + ?assertMatch({value, Value}, khash:lookup(H, K)), + ?assertMatch(Value, khash:get(H, K)); + error -> + ?assertMatch(not_found, khash:lookup(H, K)), + ?assertMatch(undefined, khash:get(H, K)) + end, + {D, H}. + +run_get3({D, H}) -> + K = random_key(D), + case dict:find(K, D) of + {ok, Value} -> + ?assertMatch({value, Value}, khash:lookup(H, K)), + ?assertMatch(Value, khash:get(H, K)); + error -> + Val = random_val(), + ?assertMatch(Val, khash:get(H, K, Val)) + end, + {D, H}. + +run_put({D0, H}) -> + K = random_key(D0), + V = random_val(), + D1 = dict:store(K, V, D0), + ?assertMatch(ok, khash:put(H, K, V)), + {D1, H}. + +run_del({D0, H}) -> + K = random_key(D0), + D1 = case dict:is_key(K, D0) of + true -> + ?assertMatch(ok, khash:del(H, K)), + dict:erase(K, D0); + false -> + ?assertMatch(not_found, khash:del(H, K)), + D0 + end, + {D1, H}. + +run_size({D, H}) -> + ?assertEqual(dict:size(D), khash:size(H)), + {D, H}. + +run_keys({D, H}) -> + DKeys = dict:fetch_keys(D), + {ok, HKeys0} = khash:keys(H), + HKeys = lists:sort(HKeys0), + ?assertEqual(lists:sort(DKeys), lists:sort(HKeys)), + {D, H}. + +run_to_list({D, H}) -> + DKVs = dict:to_list(D), + HKVs = khash:to_list(H), + ?assertEqual(lists:sort(DKVs), lists:sort(HKVs)), + {D, H}. + +weighted_choice(Items0) -> + Items = lists:sort(Items0), + Sum = lists:sum([W || {W, _} <- Items]), + Choice = random:uniform() * Sum, + weighted_choice(Items, 0.0, Choice). + +weighted_choice([], _, _) -> + throw(bad_choice); +weighted_choice([{W, _} | Rest], S, C) when W + S < C -> + weighted_choice(Rest, W+S, C); +weighted_choice([{_, I} | _], _, _) -> + I. + +random_key(D) -> + Keys = lists:usort(dict:fetch_keys(D) ++ [foo]), + lists:nth(random:uniform(length(Keys)), Keys). + +random_val() -> + gen_term:any(). http://git-wip-us.apache.org/repos/asf/couchdb-khash/blob/6938d72b/test/util.erl ---------------------------------------------------------------------- diff --git a/test/util.erl b/test/util.erl deleted file mode 100644 index 4f0bbf6..0000000 --- a/test/util.erl +++ /dev/null @@ -1,25 +0,0 @@ -%% This file is part of khash released under the MIT license. -%% See the LICENSE file for more information. -%% Copyright 2013 Cloudant, Inc <supp...@cloudant.com> - --module(util). --compile(export_all). - - -init_code_path() -> - code:add_pathz("ebin"). - - -run(Plan, Fun) -> - init_code_path(), - etap:plan(Plan), - case (catch Fun()) of - ok -> - etap:end_tests(); - Other -> - etap:diag(io_lib:format("Test died abnormally:~n~p", [Other])), - timer:sleep(500), - etap:bail(Other) - end, - ok. -