Statem test #2
7 changed files with 302 additions and 72 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ ebin
|
|||
deps
|
||||
*~
|
||||
.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, [
|
||||
{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"}}},
|
||||
{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).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("proper/include/proper.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-endif.
|
||||
|
||||
-behaviour(proper_statem).
|
||||
|
||||
-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"),
|
||||
ok = fractal_btree_writer:add(BT, <<"A">>, <<"Avalue">>),
|
||||
ok = fractal_btree_writer:add(BT, <<"B">>, <<"Bvalue">>),
|
||||
ok = fractal_btree_writer:close(BT),
|
||||
-record(state, { open = dict:new(),
|
||||
closed = dict:new() }).
|
||||
-define(SERVER, fractal_btree_drv).
|
||||
|
||||
{ok, IN} = fractal_btree_reader:open("testdata"),
|
||||
{ok, <<"Avalue">>} = fractal_btree_reader:lookup(IN, <<"A">>),
|
||||
ok = fractal_btree_reader:close(IN),
|
||||
full_test_() ->
|
||||
{setup,
|
||||
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,
|
||||
Seq = lists:seq(0, Max),
|
||||
cmd_close_args(#state { open = Open }) ->
|
||||
oneof(dict:fetch_keys(Open)).
|
||||
|
||||
{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,
|
||||
[]),
|
||||
cmd_put_args(#state { open = Open }) ->
|
||||
?LET({Name, Key, Value},
|
||||
{oneof(dict:fetch_keys(Open)), binary(), binary()},
|
||||
[Name, Key, Value]).
|
||||
|
||||
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"),
|
||||
{ok, <<"valuevalue/", 2048:128>>} = fractal_btree_reader:lookup(IN, <<2048:128>>),
|
||||
precondition(#state { open = Open }, {call, ?SERVER, put, [Name, K, V]}) ->
|
||||
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(
|
||||
fun() -> fractal_btree_reader:fold(fun(Key, <<"valuevalue/", Key/binary>>, N) ->
|
||||
N+1
|
||||
end,
|
||||
0,
|
||||
IN)
|
||||
end,
|
||||
[]),
|
||||
prop_dict_agree() ->
|
||||
?FORALL(Cmds, commands(?MODULE),
|
||||
?TRAPEXIT(
|
||||
begin
|
||||
fractal_btree_drv:start_link(),
|
||||
{History,State,Result} = run_commands(?MODULE, Cmds),
|
||||
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() ->
|
||||
|
||||
{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() ->
|
||||
test_tree() ->
|
||||
|
||||
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