Statem test #2
7 changed files with 302 additions and 72 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ ebin
|
||||||
deps
|
deps
|
||||||
*~
|
*~
|
||||||
.eunit
|
.eunit
|
||||||
|
/.fractal_btree.plt
|
||||||
|
|
36
Makefile
Normal file
36
Makefile
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
REBAR = ./rebar
|
||||||
|
DIALYZER = dialyzer
|
||||||
|
|
||||||
|
.PHONY: plt analyze all deps compile get-deps clean
|
||||||
|
|
||||||
|
all: compile
|
||||||
|
|
||||||
|
get-deps:
|
||||||
|
@$(REBAR) get-deps
|
||||||
|
|
||||||
|
compile:
|
||||||
|
@$(REBAR) compile
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@$(REBAR) clean
|
||||||
|
|
||||||
|
eunit: compile clean-test-btrees
|
||||||
|
@$(REBAR) eunit skip_deps=true
|
||||||
|
|
||||||
|
clean-test-btrees:
|
||||||
|
rm -fr .eunit/Btree_*
|
||||||
|
|
||||||
|
plt: compile
|
||||||
|
$(DIALYZER) --build_plt --output_plt .fractal_btree.plt \
|
||||||
|
-pa deps/plain_fsm/ebin \
|
||||||
|
-pa deps/ebloom/ebin \
|
||||||
|
deps/plain_fsm/ebin \
|
||||||
|
deps/ebloom/ebin \
|
||||||
|
--apps kernel stdlib
|
||||||
|
|
||||||
|
analyze: compile
|
||||||
|
$(DIALYZER) --plt .fractal_btree.plt \
|
||||||
|
-pa deps/plain_fsm/ebin \
|
||||||
|
-pa deps/ebloom/ebin \
|
||||||
|
ebin
|
||||||
|
|
|
@ -4,5 +4,7 @@
|
||||||
{deps, [
|
{deps, [
|
||||||
{plain_fsm, "1.1.*", {git, "git://github.com/uwiger/plain_fsm", {branch, "master"}}},
|
{plain_fsm, "1.1.*", {git, "git://github.com/uwiger/plain_fsm", {branch, "master"}}},
|
||||||
{ebloom, "1.0.*", {git, "git://github.com/basho/ebloom.git", {branch, "master"}}},
|
{ebloom, "1.0.*", {git, "git://github.com/basho/ebloom.git", {branch, "master"}}},
|
||||||
{basho_bench, ".*", {git, "git://github.com/basho/basho_bench.git", {branch, "master"}}}
|
{basho_bench, ".*", {git, "git://github.com/basho/basho_bench.git", {branch, "master"}}},
|
||||||
|
{proper, ".*", {git, "git://github.com/manopapad/proper.git",
|
||||||
|
{branch, "master"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
88
test/fractal_btree_drv.erl
Normal file
88
test/fractal_btree_drv.erl
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
%% @Doc Drive a set of fractal BTrees
|
||||||
|
-module(fractal_btree_drv).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
-export([open/1,
|
||||||
|
put/3,
|
||||||
|
stop/0]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
|
-record(state, { btrees = dict:new() % Map from a name to its tree
|
||||||
|
}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
call(X) ->
|
||||||
|
gen_server:call(?SERVER, X, infinity).
|
||||||
|
|
||||||
|
open(N) ->
|
||||||
|
call({open, N}).
|
||||||
|
|
||||||
|
put(N, K, V) ->
|
||||||
|
call({put, N, K, V}).
|
||||||
|
|
||||||
|
stop() ->
|
||||||
|
call(stop).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
{ok, #state{}}.
|
||||||
|
|
||||||
|
handle_call({open, N}, _, #state { btrees = D} = State) ->
|
||||||
|
case fractal_btree:open(N) of
|
||||||
|
{ok, Tree} ->
|
||||||
|
{reply, ok, State#state { btrees = dict:store(N, Tree, D)}};
|
||||||
|
Otherwise ->
|
||||||
|
{reply, {error, Otherwise}, State}
|
||||||
|
end;
|
||||||
|
handle_call({put, N, K, V}, _, #state { btrees = D} = State) ->
|
||||||
|
Tree = dict:fetch(N, D),
|
||||||
|
case fractal_btree:put(Tree, K, V) of
|
||||||
|
ok ->
|
||||||
|
{reply, ok, State};
|
||||||
|
Other ->
|
||||||
|
{reply, {error, Other}, State}
|
||||||
|
end;
|
||||||
|
handle_call(stop, _, State) ->
|
||||||
|
{stop, normal, ok, State};
|
||||||
|
handle_call(_Request, _From, State) ->
|
||||||
|
Reply = ok,
|
||||||
|
{reply, Reply, State}.
|
||||||
|
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, State) ->
|
||||||
|
cleanup_trees(State),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
%% @todo directory cleanup
|
||||||
|
cleanup_trees(#state { btrees = BTs }) ->
|
||||||
|
dict:fold(fun(_Name, Tree, ok) ->
|
||||||
|
fractal_btree:close(Tree)
|
||||||
|
end,
|
||||||
|
ok,
|
||||||
|
BTs).
|
||||||
|
|
||||||
|
|
39
test/fractal_btree_merger_tests.erl
Normal file
39
test/fractal_btree_merger_tests.erl
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
-module(fractal_btree_merger_tests).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("proper/include/proper.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
merge_test() ->
|
||||||
|
|
||||||
|
{ok, BT1} = fractal_btree_writer:open("test1"),
|
||||||
|
lists:foldl(fun(N,_) ->
|
||||||
|
ok = fractal_btree_writer:add(BT1, <<N:128>>, <<"data",N:128>>)
|
||||||
|
end,
|
||||||
|
ok,
|
||||||
|
lists:seq(1,10000,2)),
|
||||||
|
ok = fractal_btree_writer:close(BT1),
|
||||||
|
|
||||||
|
|
||||||
|
{ok, BT2} = fractal_btree_writer:open("test2"),
|
||||||
|
lists:foldl(fun(N,_) ->
|
||||||
|
ok = fractal_btree_writer:add(BT2, <<N:128>>, <<"data",N:128>>)
|
||||||
|
end,
|
||||||
|
ok,
|
||||||
|
lists:seq(2,5001,1)),
|
||||||
|
ok = fractal_btree_writer:close(BT2),
|
||||||
|
|
||||||
|
|
||||||
|
{Time,{ok,Count}} = timer:tc(fractal_btree_merger2, merge, ["test1", "test2", "test3", 10000]),
|
||||||
|
|
||||||
|
error_logger:info_msg("time to merge: ~p/sec (time=~p, count=~p)~n", [1000000/(Time/Count), Time/1000000, Count]),
|
||||||
|
|
||||||
|
ok = file:delete("test1"),
|
||||||
|
ok = file:delete("test2"),
|
||||||
|
ok = file:delete("test3"),
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
|
@ -1,100 +1,100 @@
|
||||||
-module(fractal_btree_tests).
|
-module(fractal_btree_tests).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
-include_lib("proper/include/proper.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
-behaviour(proper_statem).
|
||||||
|
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
|
|
||||||
simple_test() ->
|
-export([command/1, initial_state/0,
|
||||||
|
next_state/3, postcondition/3,
|
||||||
|
precondition/2]).
|
||||||
|
|
||||||
{ok, BT} = fractal_btree_writer:open("testdata"),
|
-record(state, { open = dict:new(),
|
||||||
ok = fractal_btree_writer:add(BT, <<"A">>, <<"Avalue">>),
|
closed = dict:new() }).
|
||||||
ok = fractal_btree_writer:add(BT, <<"B">>, <<"Bvalue">>),
|
-define(SERVER, fractal_btree_drv).
|
||||||
ok = fractal_btree_writer:close(BT),
|
|
||||||
|
|
||||||
{ok, IN} = fractal_btree_reader:open("testdata"),
|
full_test_() ->
|
||||||
{ok, <<"Avalue">>} = fractal_btree_reader:lookup(IN, <<"A">>),
|
{setup,
|
||||||
ok = fractal_btree_reader:close(IN),
|
spawn,
|
||||||
|
fun () -> ok end,
|
||||||
|
fun (_) -> ok end,
|
||||||
|
[{timeout, 120, ?_test(test_proper())},
|
||||||
|
?_test(test_tree())]}.
|
||||||
|
|
||||||
ok = file:delete("testdata").
|
qc_opts() -> [{numtests, 400}].
|
||||||
|
|
||||||
|
test_proper() ->
|
||||||
|
[?assertEqual([], proper:module(?MODULE, qc_opts()))].
|
||||||
|
|
||||||
|
|
||||||
simple1_test() ->
|
initial_state() ->
|
||||||
|
#state { }.
|
||||||
|
|
||||||
{ok, BT} = fractal_btree_writer:open("testdata"),
|
g_btree_name() ->
|
||||||
|
?LET(I, integer(1,10),
|
||||||
|
"Btree_" ++ integer_to_list(I)).
|
||||||
|
|
||||||
Max = 30*1024,
|
cmd_close_args(#state { open = Open }) ->
|
||||||
Seq = lists:seq(0, Max),
|
oneof(dict:fetch_keys(Open)).
|
||||||
|
|
||||||
{Time1,_} = timer:tc(
|
cmd_put_args(#state { open = Open }) ->
|
||||||
fun() ->
|
?LET({Name, Key, Value},
|
||||||
lists:foreach(
|
{oneof(dict:fetch_keys(Open)), binary(), binary()},
|
||||||
fun(Int) ->
|
[Name, Key, Value]).
|
||||||
ok = fractal_btree_writer:add(BT, <<Int:128>>, <<"valuevalue/", Int:128>>)
|
|
||||||
end,
|
|
||||||
Seq),
|
|
||||||
ok = fractal_btree_writer:close(BT)
|
|
||||||
end,
|
|
||||||
[]),
|
|
||||||
|
|
||||||
error_logger:info_msg("time to insert: ~p/sec~n", [1000000/(Time1/Max)]),
|
command(#state { open = Open} = S) ->
|
||||||
|
frequency(
|
||||||
|
[ {100, {call, ?SERVER, open, [g_btree_name()]}} ] ++
|
||||||
|
[ {2000, {call, ?SERVER, put, cmd_put_args(S)}}
|
||||||
|
|| dict:size(Open) > 0]).
|
||||||
|
|
||||||
{ok, IN} = fractal_btree_reader:open("testdata"),
|
precondition(#state { open = Open }, {call, ?SERVER, put, [Name, K, V]}) ->
|
||||||
{ok, <<"valuevalue/", 2048:128>>} = fractal_btree_reader:lookup(IN, <<2048:128>>),
|
dict:is_key(Name, Open);
|
||||||
|
precondition(#state { open = Open }, {call, ?SERVER, open, [Name]}) ->
|
||||||
|
not (dict:is_key(Name, Open)).
|
||||||
|
|
||||||
|
next_state(#state { open = Open} = S, _Res,
|
||||||
|
{call, ?SERVER, put, [Name, Key, Value]}) ->
|
||||||
|
S#state { open = dict:update(Name,
|
||||||
|
fun(Dict) ->
|
||||||
|
dict:store(Key, Value, Dict)
|
||||||
|
end,
|
||||||
|
Open)};
|
||||||
|
next_state(#state { open = Open} = S, _Res, {call, ?SERVER, open, [Name]}) ->
|
||||||
|
S#state { open = dict:store(Name, dict:new(), Open) }.
|
||||||
|
|
||||||
|
postcondition(_S, {call, ?SERVER, put, [_Name, _Key, _Value]}, ok) ->
|
||||||
|
true;
|
||||||
|
postcondition(_S, {call, ?SERVER, open, [_Name]}, ok) ->
|
||||||
|
true;
|
||||||
|
postcondition(_, _, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
|
||||||
{Time2,Count} = timer:tc(
|
prop_dict_agree() ->
|
||||||
fun() -> fractal_btree_reader:fold(fun(Key, <<"valuevalue/", Key/binary>>, N) ->
|
?FORALL(Cmds, commands(?MODULE),
|
||||||
N+1
|
?TRAPEXIT(
|
||||||
end,
|
begin
|
||||||
0,
|
fractal_btree_drv:start_link(),
|
||||||
IN)
|
{History,State,Result} = run_commands(?MODULE, Cmds),
|
||||||
end,
|
fractal_btree_drv:stop(),
|
||||||
[]),
|
?WHENFAIL(io:format("History: ~w\nState: ~w\nResult: ~w\n",
|
||||||
|
[History,State,Result]),
|
||||||
|
aggregate(command_names(Cmds), Result =:= ok))
|
||||||
|
end)).
|
||||||
|
|
||||||
error_logger:info_msg("time to scan: ~p/sec~n", [1000000/(Time2/Max)]),
|
%% ----------------------------------------------------------------------
|
||||||
|
|
||||||
Max = Count-1,
|
|
||||||
|
|
||||||
|
|
||||||
ok = fractal_btree_reader:close(IN),
|
|
||||||
|
|
||||||
ok = file:delete("testdata").
|
%% UNIT TESTS -----------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
merge_test() ->
|
test_tree() ->
|
||||||
|
|
||||||
{ok, BT1} = fractal_btree_writer:open("test1"),
|
|
||||||
lists:foldl(fun(N,_) ->
|
|
||||||
ok = fractal_btree_writer:add(BT1, <<N:128>>, <<"data",N:128>>)
|
|
||||||
end,
|
|
||||||
ok,
|
|
||||||
lists:seq(1,10000,2)),
|
|
||||||
ok = fractal_btree_writer:close(BT1),
|
|
||||||
|
|
||||||
|
|
||||||
{ok, BT2} = fractal_btree_writer:open("test2"),
|
|
||||||
lists:foldl(fun(N,_) ->
|
|
||||||
ok = fractal_btree_writer:add(BT2, <<N:128>>, <<"data",N:128>>)
|
|
||||||
end,
|
|
||||||
ok,
|
|
||||||
lists:seq(2,5001,1)),
|
|
||||||
ok = fractal_btree_writer:close(BT2),
|
|
||||||
|
|
||||||
|
|
||||||
{Time,{ok,Count}} = timer:tc(fractal_btree_merger2, merge, ["test1", "test2", "test3", 10000]),
|
|
||||||
|
|
||||||
error_logger:info_msg("time to merge: ~p/sec (time=~p, count=~p)~n", [1000000/(Time/Count), Time/1000000, Count]),
|
|
||||||
|
|
||||||
ok = file:delete("test1"),
|
|
||||||
ok = file:delete("test2"),
|
|
||||||
ok = file:delete("test3"),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
|
|
||||||
tree_test() ->
|
|
||||||
|
|
||||||
application:start(sasl),
|
application:start(sasl),
|
||||||
|
|
||||||
|
|
64
test/fractal_btree_writer_tests.erl
Normal file
64
test/fractal_btree_writer_tests.erl
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
-module(fractal_btree_writer_tests).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("proper/include/proper.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
simple_test() ->
|
||||||
|
|
||||||
|
{ok, BT} = fractal_btree_writer:open("testdata"),
|
||||||
|
ok = fractal_btree_writer:add(BT, <<"A">>, <<"Avalue">>),
|
||||||
|
ok = fractal_btree_writer:add(BT, <<"B">>, <<"Bvalue">>),
|
||||||
|
ok = fractal_btree_writer:close(BT),
|
||||||
|
|
||||||
|
{ok, IN} = fractal_btree_reader:open("testdata"),
|
||||||
|
{ok, <<"Avalue">>} = fractal_btree_reader:lookup(IN, <<"A">>),
|
||||||
|
ok = fractal_btree_reader:close(IN),
|
||||||
|
|
||||||
|
ok = file:delete("testdata").
|
||||||
|
|
||||||
|
|
||||||
|
simple1_test() ->
|
||||||
|
|
||||||
|
{ok, BT} = fractal_btree_writer:open("testdata"),
|
||||||
|
|
||||||
|
Max = 30*1024,
|
||||||
|
Seq = lists:seq(0, Max),
|
||||||
|
|
||||||
|
{Time1,_} = timer:tc(
|
||||||
|
fun() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(Int) ->
|
||||||
|
ok = fractal_btree_writer:add(BT, <<Int:128>>, <<"valuevalue/", Int:128>>)
|
||||||
|
end,
|
||||||
|
Seq),
|
||||||
|
ok = fractal_btree_writer:close(BT)
|
||||||
|
end,
|
||||||
|
[]),
|
||||||
|
|
||||||
|
error_logger:info_msg("time to insert: ~p/sec~n", [1000000/(Time1/Max)]),
|
||||||
|
|
||||||
|
{ok, IN} = fractal_btree_reader:open("testdata"),
|
||||||
|
{ok, <<"valuevalue/", 2048:128>>} = fractal_btree_reader:lookup(IN, <<2048:128>>),
|
||||||
|
|
||||||
|
|
||||||
|
{Time2,Count} = timer:tc(
|
||||||
|
fun() -> fractal_btree_reader:fold(fun(Key, <<"valuevalue/", Key/binary>>, N) ->
|
||||||
|
N+1
|
||||||
|
end,
|
||||||
|
0,
|
||||||
|
IN)
|
||||||
|
end,
|
||||||
|
[]),
|
||||||
|
|
||||||
|
error_logger:info_msg("time to scan: ~p/sec~n", [1000000/(Time2/Max)]),
|
||||||
|
|
||||||
|
Max = Count-1,
|
||||||
|
|
||||||
|
|
||||||
|
ok = fractal_btree_reader:close(IN),
|
||||||
|
|
||||||
|
ok = file:delete("testdata").
|
Loading…
Reference in a new issue