Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
216 lines (181 sloc) 6.99 KB
%% -------------------------------------------------------------------
%%
%% bitcask: Eric Brewer-inspired key/value store
%%
%% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
-module(bitcask_qc_fsm).
-author('Dave Smith <dizzyd@basho.com>').
-author('Justin Sheehy <justin@basho.com>').
-ifdef(EQC).
-include_lib("eqc/include/eqc.hrl").
-include_lib("eqc/include/eqc_fsm.hrl").
-include_lib("kernel/include/file.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-record(state,{ bitcask,
data = [],
keys }). %% Keys to use in the test
-define(QC_OUT(P),
eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).
-define(TEST_DIR, "/tmp/bitcask.qc").
initial_state() ->
init.
initial_state_data() ->
#state{}.
init(_S) ->
[{closed, {call, ?MODULE, set_keys, [list(key_gen())]}}].
closed(_S) ->
[{opened, {call, bitcask, open, [?TEST_DIR, [read_write, {open_timeout, 0}, sync_strategy()]]}},
{closed, {call, ?MODULE, truncate_hint, [int(), int()]}},
{closed, {call, ?MODULE, corrupt_hint, [int(), int()]}},
{closed, {call, ?MODULE, create_stale_lock, []}}].
opened(S) ->
[{closed, {call, bitcask, close, [S#state.bitcask]}},
{opened, {call, bitcask, get, [S#state.bitcask, key(S)]}},
{opened, {call, bitcask, put, [S#state.bitcask, key(S), value()]}},
{opened, {call, bitcask, delete, [S#state.bitcask, key(S)]}},
{opened, {call, bitcask, merge, [?TEST_DIR]}}
].
next_state_data(init, closed, S, _, {call, _, set_keys, [Keys]}) ->
S#state{ keys = [<<"k">> | Keys] }; % ensure always one key
next_state_data(closed, opened, S, Bcask, {call, bitcask, open, _}) ->
S#state { bitcask = Bcask };
next_state_data(opened, closed, S, _, {call, _, close, _}) ->
S#state { bitcask = undefined };
next_state_data(opened, opened, S, _, {call, bitcask, put, [_, Key, Value]}) ->
S#state { data = orddict:store(Key, Value, S#state.data) };
next_state_data(opened, opened, S, _, {call, bitcask, delete, [_, Key]}) ->
S#state { data = orddict:erase(Key, S#state.data) };
next_state_data(_From, _To, S, _Res, _Call) ->
S.
%% Precondition (for state data).
%% Precondition is checked before command is added to the command sequence
precondition(_From,_To,S,{call,_,get,[_,Key]}) ->
lists:member(Key, S#state.keys); % check the key has not been shrunk away
precondition(_From,_To,S,{call,_,put,[_,Key,_Val]}) ->
lists:member(Key, S#state.keys); % check the key has not been shrunk away
precondition(_From,_To,_S,{call,_,_,_}) ->
true.
postcondition(opened, opened, S, {call, _, get, [_, Key]}, not_found) ->
case orddict:find(Key, S#state.data) of
error ->
true;
{ok, Exp} ->
{expected, Exp, got, not_found}
end;
postcondition(opened, opened, S, {call, _, get, [_, Key]}, {ok, Value}) ->
case orddict:find(Key, S#state.data) of
{ok, Value} ->
true;
Exp ->
{expected, Exp, got, Value}
end;
postcondition(opened, opened, _S, {call, _, merge, [_TestDir]}, Res) ->
Res == ok;
postcondition(_From,_To,_S,{call,_,_,_},_Res) ->
true.
qc_test_() ->
{timeout, 120,
{setup, fun prepare/0, fun cleanup/1,
[{timeout, 120, ?_assertEqual(true,
eqc:quickcheck(?QC_OUT(prop_bitcask())))}]}}.
prepare() ->
application:load(bitcask),
application:set_env(bitcask, require_hint_crc, true).
cleanup(_) ->
application:unload(bitcask).
prop_bitcask() ->
?FORALL(Cmds, commands(?MODULE),
begin
[] = os:cmd("rm -rf " ++ ?TEST_DIR),
{H,{_State, StateData}, Res} = run_commands(?MODULE,Cmds),
case (StateData#state.bitcask) of
undefined ->
ok;
Ref ->
bitcask:close(Ref)
end,
application:unload(bitcask),
aggregate(zip(state_names(H),command_names(Cmds)),
equals(Res, ok))
end).
%% Weight for transition (this callback is optional).
%% Specify how often each transition should be chosen
weight(_From, _To,{call,_,close,_}) ->
10;
weight(_From, _To,{call,_,truncate_hint,_}) ->
10;
weight(_From, _To,{call,_,corrupt_hint,_}) ->
10;
weight(_From,_To,{call,_,_,_}) ->
100.
set_keys(_Keys) -> %% next_state sets the keys for use by key()
ok.
key_gen() ->
?SUCHTHAT(X, binary(), X /= <<>>).
key(#state{keys = Keys}) ->
elements(Keys).
value() ->
binary().
sync_strategy() ->
{sync_strategy, oneof([none, o_sync])}.
create_stale_lock() ->
Fname = filename:join(?TEST_DIR, "bitcask.write.lock"),
filelib:ensure_dir(Fname),
ok = file:write_file(Fname, "102349430239 abcdef\n").
truncate_hint(Seed, TruncBy0) ->
case filelib:wildcard(?TEST_DIR ++ "/*.hint") of
[] ->
ok;
Hints->
Hint = lists:nth(1 + (abs(Seed) rem length(Hints)), Hints),
{ok, Fi} = file:read_file_info(Hint),
{ok, Fh} = file:open(Hint, [read, write]),
TruncBy = (1 + abs(TruncBy0)) rem (Fi#file_info.size+1),
{ok, _To} = file:position(Fh, {eof, -TruncBy}),
%% io:format(user, "Truncating ~p by ~p to ~p\n", [Hint, TruncBy, _To]),
file:truncate(Fh),
file:close(Fh)
end.
corrupt_hint(Seed, CorruptAt0) ->
case filelib:wildcard(?TEST_DIR ++ "/*.hint") of
[] ->
ok;
Hints->
Hint = lists:nth(1 + (abs(Seed) rem length(Hints)), Hints),
{ok, Fi} = file:read_file_info(Hint),
{ok, Fh} = file:open(Hint, [read, write, binary]),
Size = Fi#file_info.size,
CorruptAt = (1 + abs(CorruptAt0)) rem (Size+1),
try
{ok, Pos} = file:position(Fh, {eof, -CorruptAt}),
{ok, <<Byte>>} = file:pread(Fh, Pos, 1),
BadByte = <<(bnot Byte)>>,
io:format(user, "Corrupting from ~p to ~p at ~p size ~p\n",
[Byte, BadByte, Pos, Size]),
ok = file:pwrite(Fh, Pos, BadByte)
catch
_:Reason ->
io:format(user, "corrupt failed corruptat=~p reason=~p\n",
[CorruptAt, Reason])
after
file:close(Fh)
end
end.
-endif.
Something went wrong with that request. Please try again.