From b713ce60a8ded29fcd6a3c957da8477bb256e177 Mon Sep 17 00:00:00 2001 From: Martin Sumner Date: Mon, 21 Jan 2019 10:51:07 +0000 Subject: [PATCH 1/3] Initial eqc setup --- .eqc-info | Bin 0 -> 8945 bytes current_counterexample.eqc | Bin 0 -> 1034 bytes rebar.config | 4 +- src/leveled_inker.erl | 2 +- test/leveledjc_eqc.erl | 1065 ++++++++++++++++++++++++++++++++++++ 5 files changed, 1068 insertions(+), 3 deletions(-) create mode 100644 .eqc-info create mode 100644 current_counterexample.eqc create mode 100644 test/leveledjc_eqc.erl diff --git a/.eqc-info b/.eqc-info new file mode 100644 index 0000000000000000000000000000000000000000..7dba85ac8475d61c59a9871e02448ff8a3ad8622 GIT binary patch literal 8945 zcmeI2U2GIp6vxke?QUDTE|s>T<)b7LY79c5NX3V~s81*wN=OW@cV}*QW|^Hk%+A(Q zNe~I~$*55@Au&YM7aLyuNI?*tlbiqFsH^RxL=6Ajho+*j8<=0Pw_F zdgz~_hwV?Ij$1nc()xT5P`=do-_4a1eEzd0sd^y}@E9ulE_SI&mBoct!F!m{nJ~)4 z!mb7FE*`}$wn}Cm?=fwSHw$_5Q4}D>)-BLYBpu`MU!%~9>YJvy4jS;uZhoo_b%hZu{p-P?d5r)*ABv=9518`pC$u#8JXdAE&Hq}2d+~IM~KS{2G4*ZmD5bfowik< zy8XC1vijV|+fSc+x@+{$_m53H(?c(Ivx&H}R-*2LwtmywMS9&M9<8QB9O_MUn0i{Z^b!5h=Bp8Dq`Tk8`8?5A$$AcLRb zQ*64lg3}VtlD##&~q4Z@O zFaGt?#pA}T^7X?Vu4UMy8 zqdE&P5Y=?R!%>|BI2_fBLE#@}b$cD4#`K+_$Mh1&#B?X*V!8`f#&kCf#q?e9LQF4J n#D`(s{@rR^-=oKMPbRLH<>Gqz%D7%JB=jP|f88L!stf-D^C8Rz literal 0 HcmV?d00001 diff --git a/current_counterexample.eqc b/current_counterexample.eqc new file mode 100644 index 0000000000000000000000000000000000000000..775fd177f75722d4ee2fa79f960f1545d15c6e25 GIT binary patch literal 1034 zcmbW0&u>jZ6vyZOnD?$m(T#3|*htwSMQdXtRzw7m2-mzj^X`w%yK_5t?xQwB(^!&V z(?lA@s_8~-BvkwdqSz?@0||n}s%K{UTJho~dXu?1bMBcl-}5==PI82ho46EtB^%e9 zn%>}+po~-0&sm#sMzbhn<5A+7FlEBqL=q?QDe@!2yFw}2nC7Vp8zdTIIn{~bFczU4 zYnzSEu|W!k@B!`pk{ehmIxGt#A&$M%H*nxLCsm}(wJREW8AhdAJHgkX(S0X2#@uwvr5%INM zYcQ!&Mhu(I#tw@;QU4=Tkij+j!kx~&a}TZ^efQ<#bl<6;OMB+=$O|$wJJtVn`<;bP z3nzm&5VRTA>fz_?kaUA8> %% @doc %% Shutdown any snapshots before closing the store shutdown_snapshots(Snapshots) -> - lists:foreach(fun({Snap, _SQN}) -> ok = ink_close(Snap) end, Snapshots). + lists:foreach(fun({Snap, _TS, _SQN}) -> ok = ink_close(Snap) end, Snapshots). -spec shutdown_manifest(leveled_imanifest:manifest()) -> ok. %% @doc diff --git a/test/leveledjc_eqc.erl b/test/leveledjc_eqc.erl new file mode 100644 index 0000000..eec9a64 --- /dev/null +++ b/test/leveledjc_eqc.erl @@ -0,0 +1,1065 @@ +%% ------------------------------------------------------------------- +%% +%% leveld_eqc: basic statem for doing things to leveled +%% +%% 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(leveledjc_eqc). + +-include_lib("eqc/include/eqc.hrl"). +-include_lib("eqc/include/eqc_statem.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("../include/leveled.hrl"). + +-compile([export_all, nowarn_export_all]). + +-define(NUMTESTS, 1000). +-define(QC_OUT(P), + eqc:on_output(fun(Str, Args) -> + io:format(user, Str, Args) end, P)). + +-define(CMD_VALID(State, Cmd, True, False), + case is_valid_cmd(State, Cmd) of + true -> True; + false -> False + end). + + +eqc_test_() -> + Timeout = 50, + {timeout, max(2 * Timeout, Timeout + 10), + ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(Timeout, ?QC_OUT(prop_db()))))}. + +run() -> + run(?NUMTESTS). + +run(Count) -> + eqc:quickcheck(eqc:numtests(Count, prop_db())). + +check() -> + eqc:check(prop_db()). + +iff(B1, B2) -> B1 == B2. +implies(B1, B2) -> (not B1 orelse B2). + +%% start_opts should not be added to this map, it is added only when the system is started the first time. +initial_state() -> + #{dir => {var, dir}, + sut => sut, + leveled => undefined, %% to make adapt happy after failing pre/1 + counter => 0, + model => orddict:new(), + previous_keys => [], + deleted_keys => [], + folders => [] + }. + +%% --- Operation: init_backend --- +init_backend_pre(S) -> + not is_leveled_open(S). + +init_backend_args(#{dir := Dir, sut := Name} = S) -> + case maps:get(start_opts, S, undefined) of + undefined -> + [ default(?RIAK_TAG, ?STD_TAG), %% Just test one tag at a time + [{root_path, Dir}, {log_level, error} | gen_opts()], Name ]; + Opts -> + %% root_path is part of existing options + [ maps:get(tag, S), Opts, Name ] + end. + +init_backend_pre(S, [Tag, Options, _]) -> + %% for shrinking + PreviousOptions = maps:get(start_opts, S, undefined), + maps:get(tag, S, Tag) == Tag andalso + PreviousOptions == undefined orelse PreviousOptions == Options. + +init_backend_adapt(S, [Tag, Options, Name]) -> + [ maps:get(tag, S, Tag), maps:get(start_opts, S, Options), Name]. + +%% @doc init_backend - The actual operation +%% Start the database and read data from disk +init_backend(_Tag, Options, Name) -> + case leveled_bookie:book_start(Options) of + {ok, Bookie} -> + unlink(Bookie), + erlang:register(Name, Bookie), + Bookie; + Error -> Error + end. + +init_backend_next(S, LevelEdPid, [Tag, Options, _]) -> + S#{leveled => LevelEdPid, start_opts => Options, tag => Tag}. + +init_backend_post(_S, [_, _Options, _], LevelEdPid) -> + is_pid(LevelEdPid). + +init_backend_features(_S, [_Tag, Options, _], _Res) -> + [{start_options, Options}]. + + +%% --- Operation: stop --- +stop_pre(S) -> + is_leveled_open(S). + +%% @doc stop_args - Argument generator +stop_args(#{leveled := Pid}) -> + [Pid]. + +stop_pre(#{leveled := Leveled}, [Pid]) -> + %% check during shrinking + Pid == Leveled. + +stop_adapt(#{leveled := Leveled}, [_]) -> + [Leveled]. + +%% @doc stop - The actual operation +%% Stop the server, but the values are still on disk +stop(Pid) -> + ok = leveled_bookie:book_close(Pid). + +stop_next(S, _Value, [_Pid]) -> + S#{leveled => undefined, + folders => [], + used_folders => [], + stop_folders => maps:get(folders, S, []) ++ maps:get(used_folders, S, [])}. + +stop_post(_S, [Pid], _Res) -> + Mon = erlang:monitor(process, Pid), + receive + {'DOWN', Mon, _Type, Pid, _Info} -> + true + after 5000 -> + {still_a_pid, Pid} + end. + + +%% --- Operation: put --- +put_pre(S) -> + is_leveled_open(S). + +put_args(#{leveled := Pid, previous_keys := PK, tag := Tag}) -> + ?LET(Categories, gen_categories(Tag), + ?LET({{Key, Bucket}, Value, IndexSpec, MetaData}, + {gen_key_in_bucket(PK), gen_val(), [{add, Cat, gen_index_value()} || Cat <- Categories ], []}, + case Tag of + ?STD_TAG -> [Pid, Bucket, Key, Value, IndexSpec, elements([none, Tag])]; + ?RIAK_TAG -> + Obj = testutil:riak_object(Bucket, Key, Value, MetaData), + [Pid, Bucket, Key, Obj, IndexSpec, Tag] + end)). + +put_pre(#{leveled := Leveled}, [Pid, _Bucket, _Key, _Value, _, _]) -> + Pid == Leveled. + +put_adapt(#{leveled := Leveled}, [_, Bucket, Key, Value, Spec, Tag]) -> + [ Leveled, Bucket, Key, Value, Spec, Tag ]. + +%% @doc put - The actual operation +put(Pid, Bucket, Key, Value, Spec, none) -> + leveled_bookie:book_put(Pid, Bucket, Key, Value, Spec); +put(Pid, Bucket, Key, Value, Spec, Tag) -> + leveled_bookie:book_put(Pid, Bucket, Key, Value, Spec, Tag). + +put_next(#{model := Model, previous_keys := PK} = S, _Value, [_Pid, Bucket, Key, Value, Spec, _Tag]) -> + ?CMD_VALID(S, put, + begin + NewSpec = + case orddict:find({Bucket, Key}, Model) of + error -> merge_index_spec([], Spec); + {ok, {_, OldSpec}} -> + merge_index_spec(OldSpec, Spec) + end, + S#{model => orddict:store({Bucket, Key}, {Value, NewSpec}, Model), + previous_keys => PK ++ [{Key, Bucket}]} + end, + S). + +put_post(S, [_, _, _, _, _, _], Res) -> + ?CMD_VALID(S, put, eq(Res, ok), eq(Res, {unsupported_message, put})). + +put_features(#{previous_keys := PK} = S, [_Pid, Bucket, Key, _Value, _, Tag], _Res) -> + ?CMD_VALID(S, put, + case + lists:member({Key, Bucket}, PK) of + true -> + [{put, update, Tag}]; + false -> + [{put, insert, Tag}] + end, + [{put, unsupported}]). + +merge_index_spec(Spec, []) -> + Spec; +merge_index_spec(Spec, [{add, Cat, Idx} | Rest]) -> + merge_index_spec(lists:delete({Cat, Idx}, Spec) ++ [{Cat, Idx}], Rest); +merge_index_spec(Spec, [{remove, Cat, Idx} | Rest]) -> + merge_index_spec(lists:delete({Cat, Idx}, Spec), Rest). + + +%% --- Operation: get --- +get_pre(S) -> + is_leveled_open(S). + +get_args(#{leveled := Pid, previous_keys := PK, tag := Tag}) -> + ?LET({Key, Bucket}, gen_key_in_bucket(PK), + [Pid, Bucket, Key, case Tag of ?STD_TAG -> default(none, Tag); _ -> Tag end]). + +%% @doc get - The actual operation +get(Pid, Bucket, Key, none) -> + leveled_bookie:book_get(Pid, Bucket, Key); +get(Pid, Bucket, Key, Tag) -> + leveled_bookie:book_get(Pid, Bucket, Key, Tag). + +get_pre(#{leveled := Leveled}, [Pid, _Bucket, _Key, _Tag]) -> + Pid == Leveled. + +get_adapt(#{leveled := Leveled}, [_, Bucket, Key, Tag]) -> + [Leveled, Bucket, Key, Tag]. + +get_post(#{model := Model} = S, [_Pid, Bucket, Key, Tag], Res) -> + ?CMD_VALID(S, get, + case Res of + {ok, _} -> + {ok, {Value, _}} = orddict:find({Bucket, Key}, Model), + eq(Res, {ok, Value}); + not_found -> + %% Weird to be able to supply a tag, but must be STD_TAG... + Tag =/= ?STD_TAG orelse orddict:find({Bucket, Key}, Model) == error + end, + eq(Res, {unsupported_message, get})). + +get_features(#{deleted_keys := DK, previous_keys := PK}, [_Pid, Bucket, Key, _Tag], Res) -> + case Res of + not_found -> + [{get, not_found, deleted} || lists:member({Key, Bucket}, DK)] ++ + [{get, not_found, not_inserted} || not lists:member({Key, Bucket}, PK)]; + {ok, B} when is_binary(B) -> + [{get, found}]; + {unsupported_message, _} -> + [{get, unsupported}] + end. + +%% --- Operation: mput --- +mput_pre(S) -> + is_leveled_open(S). + +%% @doc mput_args - Argument generator +%% Specification says: duplicated should be removed +%% "%% The list should be de-duplicated before it is passed to the bookie." +%% Wether this means that keys should be unique or even Action and values is unclear. +%% Slack discussion: +%% `[{add, B1, K1, SK1}, {add, B1, K1, SK2}]` should be fine (same bucket and key, different subkey) +%% +%% Really weird to have to specify a value in case of a remove action +mput_args(#{leveled := Pid, previous_keys := PK}) -> + ?LET(Objs, list({gen_key_in_bucket(PK), nat()}), + [Pid, [ {weighted_default({5, add}, {1, remove}), Bucket, Key, SubKey, gen_val()} || {{Key, Bucket}, SubKey} <- Objs ]]). + + +mput_pre(#{leveled := Leveled}, [Pid, ObjSpecs]) -> + Pid == Leveled andalso no_key_dups(ObjSpecs) == ObjSpecs. + +mput_adapt(#{leveled := Leveled}, [_, ObjSpecs]) -> + [ Leveled, no_key_dups(ObjSpecs) ]. + +mput(Pid, ObjSpecs) -> + leveled_bookie:book_mput(Pid, ObjSpecs). + +mput_next(S, _, [_Pid, ObjSpecs]) -> + ?CMD_VALID(S, mput, + lists:foldl(fun({add, Bucket, Key, _SubKey, Value}, #{model := Model, previous_keys := PK} = Acc) -> + Acc#{model => orddict:store({Bucket, Key}, {Value, []}, Model), + previous_keys => PK ++ [{Key, Bucket}]}; + ({remove, Bucket, Key, _SubKey, _Value}, #{model := Model} = Acc) -> + Acc#{model => orddict:erase({Bucket, Key}, Model)} + end, S, ObjSpecs), + S). + +mput_post(S, [_, _], Res) -> + ?CMD_VALID(S, mput, eq(Res, ok), eq(Res, {unsupported_message, mput})). + +mput_features(S, [_Pid, ObjSpecs], _Res) -> + ?CMD_VALID(S, mput, + {mput, [ element(1, ObjSpec) || ObjSpec <- ObjSpecs ]}, + [{mput, unsupported}]). + +%% --- Operation: head --- +head_pre(S) -> + is_leveled_open(S). + +head_args(#{leveled := Pid, previous_keys := PK, tag := Tag}) -> + ?LET({Key, Bucket}, gen_key_in_bucket(PK), + [Pid, Bucket, Key, Tag]). + +head_pre(#{leveled := Leveled}, [Pid, _Bucket, _Key, _Tag]) -> + Pid == Leveled. + +head_adapt(#{leveled := Leveled}, [_, Bucket, Key, Tag]) -> + [Leveled, Bucket, Key, Tag]. + +head(Pid, Bucket, Key, none) -> + leveled_bookie:book_head(Pid, Bucket, Key); +head(Pid, Bucket, Key, Tag) -> + leveled_bookie:book_head(Pid, Bucket, Key, Tag). + +head_post(#{model := Model} = S, [_Pid, Bucket, Key, Tag], Res) -> + ?CMD_VALID(S, head, + case Res of + {ok, _MetaData} -> + orddict:find({Bucket, Key}, Model) =/= error; + not_found -> + %% Weird to be able to supply a tag, but must be STD_TAG... + implies(lists:member(maps:get(start_opts, S), [{head_only, with_lookup}]), + lists:member(Tag, [?STD_TAG, none, ?HEAD_TAG])) orelse + orddict:find({Bucket, Key}, Model) == error; + {unsupported_message, head} -> + Tag =/= ?HEAD_TAG + end, + eq(Res, {unsupported_message, head})). + +head_features(#{deleted_keys := DK, previous_keys := PK}, [_Pid, Bucket, Key, _Tag], Res) -> + case Res of + not_found -> + [{head, not_found, deleted} || lists:member({Key, Bucket}, DK)] ++ + [{head, not_found, not_inserted} || not lists:member({Key, Bucket}, PK)]; + {ok, {_, _, _}} -> %% Metadata + [{head, found}]; + {ok, Bin} when is_binary(Bin) -> + [{head, found_riak_object}]; + {unsupported_message, _} -> + [{head, unsupported}] + end. + + +%% --- Operation: delete --- +delete_pre(S) -> + is_leveled_open(S). + +delete_args(#{leveled := Pid, previous_keys := PK, tag := Tag}) -> + ?LET({Key, Bucket}, gen_key_in_bucket(PK), + [Pid, Bucket, Key, [], Tag]). + +delete_pre(#{leveled := Leveled, model := Model}, [Pid, Bucket, Key, Spec, _Tag]) -> + Pid == Leveled andalso + case orddict:find({Bucket, Key}, Model) of + error -> true; + {ok, {_, OldSpec}} -> + Spec == OldSpec + end. + +delete_adapt(#{leveled := Leveled, model := Model}, [_, Bucket, Key, Spec, Tag]) -> + NewSpec = + case orddict:find({Bucket, Key}, Model) of + error -> Spec; + {ok, {_, OldSpec}} -> + Spec == OldSpec + end, + [ Leveled, Bucket, Key, NewSpec, Tag ]. + +delete(Pid, Bucket, Key, Spec, ?STD_TAG) -> + leveled_bookie:book_delete(Pid, Bucket, Key, Spec); +delete(Pid, Bucket, Key, Spec, Tag) -> + leveled_bookie:book_put(Pid, Bucket, Key, delete, Spec, Tag). + +delete_next(#{model := Model, deleted_keys := DK} = S, _Value, [_Pid, Bucket, Key, _, _]) -> + ?CMD_VALID(S, delete, + S#{model => orddict:erase({Bucket, Key}, Model), + deleted_keys => DK ++ [{Key, Bucket} || orddict:is_key({Key, Bucket}, Model)]}, + S). + +delete_post(S, [_Pid, _Bucket, _Key, _, _], Res) -> + ?CMD_VALID(S, delete, + eq(Res, ok), + case Res of + {unsupported_message, _} -> true; + _ -> Res + end). + +delete_features(#{previous_keys := PK} = S, [_Pid, Bucket, Key, _, _], _Res) -> + ?CMD_VALID(S, delete, + case lists:member({Key, Bucket}, PK) of + true -> + [{delete, existing}]; + false -> + [{delete, none_existing}] + end, + [{delete, unsupported}]). + +%% --- Operation: is_empty --- +is_empty_pre(S) -> + is_leveled_open(S). + +is_empty_args(#{leveled := Pid, tag := Tag}) -> + [Pid, Tag]. + + +is_empty_pre(#{leveled := Leveled}, [Pid, _]) -> + Pid == Leveled. + +is_empty_adapt(#{leveled := Leveled}, [_, Tag]) -> + [Leveled, Tag]. + +%% @doc is_empty - The actual operation +is_empty(Pid, Tag) -> + leveled_bookie:book_isempty(Pid, Tag). + +is_empty_post(#{model := Model}, [_Pid, _Tag], Res) -> + Size = orddict:size(Model), + case Res of + true -> eq(0, Size); + false when Size == 0 -> expected_empty; + false when Size > 0 -> true + end. + +is_empty_features(_S, [_Pid, _], Res) -> + [{empty, Res}]. + +%% --- Operation: drop --- +drop_pre(S) -> + is_leveled_open(S). + +drop_args(#{leveled := Pid, dir := Dir} = S) -> + ?LET([Tag, _, Name], init_backend_args(S), + [Pid, Tag, [{root_path, Dir} | gen_opts()], Name]). + +drop_pre(#{leveled := Leveled} = S, [Pid, Tag, Opts, Name]) -> + Pid == Leveled andalso init_backend_pre(S, [Tag, Opts, Name]). + +drop_adapt(#{leveled := Leveled} = S, [_Pid, Tag, Opts, Name]) -> + [Leveled | init_backend_adapt(S, [Tag, Opts, Name])]. + +%% @doc drop - The actual operation +%% Remove fles from disk (directory structure may remain) and start a new clean database +drop(Pid, Tag, Opts, Name) -> + Mon = erlang:monitor(process, Pid), + ok = leveled_bookie:book_destroy(Pid), + receive + {'DOWN', Mon, _Type, Pid, _Info} -> + init_backend(Tag, Opts, Name) + after 5000 -> + {still_alive, Pid, Name} + end. + +drop_next(S, Value, [Pid, Tag, Opts, Name]) -> + S1 = stop_next(S, Value, [Pid]), + init_backend_next(S1#{model => orddict:new()}, + Value, [Tag, Opts, Name]). + +drop_post(_S, [_Pid, _Tag, _Opts, _], Res) -> + case is_pid(Res) of + true -> true; + false -> Res + end. + +drop_features(#{model := Model}, [_Pid, _Tag, _Opts, _], _Res) -> + Size = orddict:size(Model), + [{drop, empty} || Size == 0 ] ++ + [{drop, small} || Size > 0 andalso Size < 20 ] ++ + [{drop, medium} || Size >= 20 andalso Size < 1000 ] ++ + [{drop, large} || Size >= 1000 ]. + + + +%% --- Operation: kill --- +%% Test that killing the root Pid of leveled has the same effect as closing it nicely +%% that means, we don't loose data! Not even when parallel successful puts are going on. +kill_pre(S) -> + is_leveled_open(S). + +kill_args(#{leveled := Pid}) -> + [Pid]. + +kill_pre(#{leveled := Leveled}, [Pid]) -> + Pid == Leveled. + +kill_adapt(#{leveled := Leveled}, [_]) -> + [ Leveled ]. + +kill(Pid) -> + exit(Pid, kill), + timer:sleep(1). + +kill_next(S, Value, [Pid]) -> + stop_next(S, Value, [Pid]). + +%% Testing fold: +%% Note async and sync mode! +%% see https://github.com/martinsumner/riak_kv/blob/mas-2.2.5-tictactaae/src/riak_kv_leveled_backend.erl#L238-L419 + +%% --- Operation: index folding --- +indexfold_pre(S) -> + is_leveled_open(S). + +indexfold_args(#{leveled := Pid, counter := Counter, previous_keys := PK}) -> + ?LET({Key, Bucket}, gen_key_in_bucket(PK), + [Pid, default(Bucket, {Bucket, Key}), gen_foldacc(3), + ?LET({[N], M}, {gen_index_value(), choose(0,2)}, {gen_category(), [N], [N+M]}), + {bool(), + oneof([undefined, gen_index_value()])}, + Counter %% add a unique counter + ]). + +indexfold_pre(#{leveled := Leveled}, [Pid, _Constraint, _FoldAccT, _Range, _TermHandling, _Counter]) -> + %% Make sure we operate on an existing Pid when shrinking + %% Check start options validity as well? + Pid == Leveled. + +indexfold_adapt(#{leveled := Leveled}, [_, Constraint, FoldAccT, Range, TermHandling, Counter]) -> + %% Keep the counter! + [Leveled, Constraint, FoldAccT, Range, TermHandling, Counter]. + +indexfold(Pid, Constraint, FoldAccT, Range, {_, undefined} = TermHandling, _Counter) -> + {async, Folder} = leveled_bookie:book_indexfold(Pid, Constraint, FoldAccT, Range, TermHandling), + Folder; +indexfold(Pid, Constraint, FoldAccT, Range, {ReturnTerms, RegExp}, _Counter) -> + {ok, RE} = re:compile(RegExp), + {async, Folder} = leveled_bookie:book_indexfold(Pid, Constraint, FoldAccT, Range, {ReturnTerms, RE}), + Folder. + +indexfold_next(#{folders := Folders} = S, SymFolder, + [_, Constraint, {Fun, Acc}, {Category, From, To}, {ReturnTerms, RegExp}, Counter]) -> + ConstraintFun = + fun(B, K, Bool) -> + case Constraint of + {B, KStart} -> not Bool orelse K >= KStart; + B -> true; + _ -> false + end + end, + S#{folders => + Folders ++ + [#{counter => Counter, + type => indexfold, + folder => SymFolder, + reusable => true, + result => fun(Model) -> + Select = + lists:sort( + orddict:fold(fun({B, K}, {_V, Spec}, A) -> + [ {B, {Idx, K}} + || {Cat, Idx} <- Spec, + Idx >= From, Idx =< To, + Cat == Category, + ConstraintFun(B, K, Idx == From), + RegExp == undefined orelse string:find(Idx, RegExp) =/= nomatch + ] ++ A + end, [], Model)), + lists:foldl(fun({B, NK}, A) when ReturnTerms -> + Fun(B, NK, A); + ({B, {_, NK}}, A) -> + Fun(B, NK, A) + end, Acc, Select) + end + }], + counter => Counter + 1}. + +indexfold_post(_S, _, Res) -> + is_function(Res). + +indexfold_features(_S, [_Pid, Constraint, FoldAccT, _Range, {ReturnTerms, _}, _Counter], _Res) -> + [{foldAccT, FoldAccT}] ++ %% This will be extracted for printing later + [{index_fold, bucket} || not is_tuple(Constraint) ] ++ + [{index_fold, bucket_and_primary_key} || is_tuple(Constraint)] ++ + [{index_fold, return_terms, ReturnTerms} ]. + + + + +%% --- Operation: keylist folding --- +%% slack discussion: "`book_keylist` only passes `Bucket` and `Key` into the accumulator, ignoring SubKey - +%% so I don't think this can be used in head_only mode to return results that make sense" +%% +%% There are also keylist functions that take a specific bucket and range into account. Not considered yet. +keylistfold_pre(S) -> + is_leveled_open(S). + +keylistfold_args(#{leveled := Pid, counter := Counter, tag := Tag}) -> + [Pid, Tag, gen_foldacc(3), + Counter %% add a unique counter + ]. + +keylistfold_pre(#{leveled := Leveled}, [Pid, _Tag, _FoldAccT, _Counter]) -> + %% Make sure we operate on an existing Pid when shrinking + %% Check start options validity as well? + Pid == Leveled. + +keylistfold_adapt(#{leveled := Leveled}, [_, Tag, FoldAccT, Counter]) -> + %% Keep the counter! + [Leveled, Tag, FoldAccT, Counter]. + +keylistfold(Pid, Tag, FoldAccT, _Counter) -> + {async, Folder} = leveled_bookie:book_keylist(Pid, Tag, FoldAccT), + Folder. + +keylistfold_next(#{folders := Folders, model := Model} = S, SymFolder, + [_, _Tag, {Fun, Acc}, Counter]) -> + S#{folders => + Folders ++ + [#{counter => Counter, + type => keylist, + folder => SymFolder, + reusable => false, + result => fun(_) -> orddict:fold(fun({B, K}, _V, A) -> Fun(B, K, A) end, Acc, Model) end + }], + counter => Counter + 1}. + +keylistfold_post(_S, _, Res) -> + is_function(Res). + +keylistfold_features(_S, [_Pid, _Tag, FoldAccT, _Counter], _Res) -> + [{foldAccT, FoldAccT}]. %% This will be extracted for printing later + + +%% --- Operation: bucketlistfold --- +bucketlistfold_pre(S) -> + is_leveled_open(S). + +bucketlistfold_args(#{leveled := Pid, counter := Counter, tag := Tag}) -> + [Pid, Tag, gen_foldacc(2), elements([first, all]), Counter]. + +bucketlistfold_pre(#{leveled := Leveled}, [Pid, _Tag, _FoldAccT, _Constraints, _]) -> + Pid == Leveled. + +bucketlistfold_adapt(#{leveled := Leveled}, [_Pid, Tag, FoldAccT, Constraints, Counter]) -> + [Leveled, Tag, FoldAccT, Constraints, Counter]. + +bucketlistfold(Pid, Tag, FoldAccT, Constraints, _) -> + {async, Folder} = leveled_bookie:book_bucketlist(Pid, Tag, FoldAccT, Constraints), + Folder. + +bucketlistfold_next(#{folders := Folders} = S, SymFolder, + [_, _, {Fun, Acc}, Constraints, Counter]) -> + S#{folders => + Folders ++ + [#{counter => Counter, + type => bucketlist, + folder => SymFolder, + reusable => true, + result => fun(Model) -> + Bs = orddict:fold(fun({B, _K}, _V, A) -> A ++ [B || not lists:member(B, A)] end, [], Model), + case {Constraints, Bs} of + {all, _} -> + lists:foldl(fun(B, A) -> Fun(B, A) end, Acc, Bs); + {first, []} -> + Acc; + {first, [First|_]} -> + lists:foldl(fun(B, A) -> Fun(B, A) end, Acc, [First]) + end + end + }], + counter => Counter + 1}. + +bucketlistfold_post(_S, [_Pid, _Tag, _FoldAccT, _Constraints, _], Res) -> + is_function(Res). + +bucketlistfold_features(_S, [_Pid, _Tag, FoldAccT, _Constraints, _], _Res) -> + [ {foldAccT, FoldAccT} ]. + +%% --- Operation: objectfold --- +objectfold_pre(S) -> + is_leveled_open(S). + +objectfold_args(#{leveled := Pid, counter := Counter, tag := Tag}) -> + [Pid, Tag, gen_foldacc(4), bool(), Counter]. + +objectfold_pre(#{leveled := Leveled}, [Pid, _Tag, _FoldAccT, _Snapshot, _Counter]) -> + Leveled == Pid. + +objectfold_adapt(#{leveled := Leveled}, [_Pid, Tag, FoldAccT, Snapshot, Counter]) -> + [Leveled, Tag, FoldAccT, Snapshot, Counter]. + +objectfold(Pid, Tag, FoldAccT, Snapshot, _Counter) -> + {async, Folder} = leveled_bookie:book_objectfold(Pid, Tag, FoldAccT, Snapshot), + Folder. + +objectfold_next(#{folders := Folders, model := Model} = S, SymFolder, + [_Pid, _Tag, {Fun, Acc}, Snapshot, Counter]) -> + S#{folders => + Folders ++ + [#{counter => Counter, + type => objectfold, + folder => SymFolder, + reusable => not Snapshot, + result => fun(M) -> + OnModel = + case Snapshot of + true -> Model; + false -> M + end, + Objs = orddict:fold(fun({B, K}, {V, _}, A) -> [{B, K, V} | A] end, [], OnModel), + lists:foldr(fun({B, K, V}, A) -> Fun(B, K, V, A) end, Acc, Objs) + end + }], + counter => Counter + 1}. + +objectfold_post(_S, [_Pid, _Tag, _FoldAccT, _Snapshot, _Counter], Res) -> + is_function(Res). + +objectfold_features(_S, [_Pid, _Tag, FoldAccT, _Snapshot, _Counter], _Res) -> + [{foldAccT, FoldAccT}]. %% This will be extracted for printing later + + + + +%% --- Operation: fold_run --- +fold_run_pre(S) -> + maps:get(folders, S, []) =/= []. + +fold_run_args(#{folders := Folders}) -> + ?LET(#{counter := Counter, folder := Folder}, elements(Folders), + [Counter, Folder]). + +fold_run_pre(#{folders := Folders}, [Counter, _Folder]) -> + %% Ensure membership even under shrinking + %% Counter is fixed at first generation and does not shrink! + get_foldobj(Folders, Counter) =/= undefined. + +fold_run(_, Folder) -> + catch Folder(). + +fold_run_next(#{folders := Folders} = S, _Value, [Counter, _Folder]) -> + %% leveled_runner comment: "Iterators should de-register themselves from the Penciller on completion." + FoldObj = get_foldobj(Folders, Counter), + case FoldObj of + #{reusable := false} -> + UsedFolders = maps:get(used_folders, S, []), + S#{folders => Folders -- [FoldObj], + used_folders => UsedFolders ++ [FoldObj]}; + _ -> + S + end. + +fold_run_post(#{folders := Folders, leveled := Leveled, model := Model}, [Count, _], Res) -> + case Leveled of + undefined -> + is_exit(Res); + _ -> + #{result := ResFun} = get_foldobj(Folders, Count), + eq(Res, ResFun(Model)) + end. + +fold_run_features(#{folders := Folders, leveled := Leveled}, [Count, _Folder], Res) -> + #{type := Type} = get_foldobj(Folders, Count), + [ {fold_run, Type} || Leveled =/= undefined ] ++ + [ fold_run_on_stopped_leveled || Leveled == undefined ] ++ + [ {fold_run, found_list, length(Res)}|| is_list(Res) ] ++ + [ {fold_run, found_integer}|| is_integer(Res) ]. + + +%% --- Operation: fold_run on already used folder --- +%% A fold that has already ran to completion should results in an exception when re-used. +%% leveled_runner comment: "Iterators should de-register themselves from the Penciller on completion." +noreuse_fold_pre(S) -> + maps:get(used_folders, S, []) =/= []. + +noreuse_fold_args(#{used_folders := Folders}) -> + ?LET(#{counter := Counter, folder := Folder}, elements(Folders), + [Counter, Folder]). + +noreuse_fold_pre(S, [Counter, _Folder]) -> + %% Ensure membership even under shrinking + %% Counter is fixed at first generation and does not shrink! + lists:member(Counter, + [ maps:get(counter, Used) || Used <- maps:get(used_folders, S, []) ]). + +noreuse_fold(_, Folder) -> + catch Folder(). + +noreuse_fold_post(_S, [_, _], Res) -> + is_exit(Res). + +noreuse_fold_features(_, [_, _], _) -> + [ reuse_fold ]. + + +%% --- Operation: fold_run on folder that survived a crash --- +%% A fold that has already ran to completion should results in an exception when re-used. +stop_fold_pre(S) -> + maps:get(stop_folders, S, []) =/= []. + +stop_fold_args(#{stop_folders := Folders}) -> + ?LET(#{counter := Counter, folder := Folder}, elements(Folders), + [Counter, Folder]). + +stop_fold_pre(S, [Counter, _Folder]) -> + %% Ensure membership even under shrinking + %% Counter is fixed at first generation and does not shrink! + lists:member(Counter, + [ maps:get(counter, Used) || Used <- maps:get(stop_folders, S, []) ]). + +stop_fold(_, Folder) -> + catch Folder(). + +stop_fold_post(_S, [_Counter, _], Res) -> + is_exit(Res). + +stop_fold_features(S, [_, _], _) -> + [ case maps:get(leveled, S) of + undefined -> + stop_fold_when_closed; + _ -> + stop_fold_when_open + end ]. + + +weight(#{previous_keys := []}, Command) when Command == get; + Command == delete -> + 1; +weight(S, C) when C == get; + C == put -> + ?CMD_VALID(S, put, 10, 1); +weight(_S, stop) -> + 1; +weight(_, _) -> + 1. + + +is_valid_cmd(S, put) -> + not in_head_only_mode(S); +is_valid_cmd(S, delete) -> + is_valid_cmd(S, put); +is_valid_cmd(S, get) -> + not in_head_only_mode(S); +is_valid_cmd(S, head) -> + not lists:member({head_only, no_lookup}, maps:get(start_opts, S, [])); +is_valid_cmd(S, mput) -> + in_head_only_mode(S). + + + +%% @doc check that the implementation of leveled is equivalent to a +%% sorted dict at least +-spec prop_db() -> eqc:property(). +prop_db() -> + Dir = "./leveled_data", + eqc:dont_print_counterexample( + ?LET(Shrinking, parameter(shrinking, false), + ?FORALL({Kind, Cmds}, oneof([{seq, more_commands(20, commands(?MODULE))}, + {par, more_commands(2, parallel_commands(?MODULE))}]), + begin + delete_level_data(Dir), + ?IMPLIES(empty_dir(Dir), + ?ALWAYS(if Shrinking -> 10; true -> 1 end, + begin + Procs = erlang:processes(), + StartTime = erlang:system_time(millisecond), + + RunResult = execute(Kind, Cmds, [{dir, Dir}]), + %% Do not extract the 'state' from this tuple, since parallel commands + %% miss the notion of final state. + CallFeatures = [ Feature || Feature <- call_features(history(RunResult)), + not is_foldaccT(Feature), + not (is_tuple(Feature) andalso element(1, Feature) == start_options) + ], + StartOptionFeatures = [ lists:keydelete(root_path, 1, Feature) || {start_options, Feature} <- call_features(history(RunResult)) ], + + case whereis(maps:get(sut, initial_state())) of + undefined -> delete_level_data(Dir); + Pid when is_pid(Pid) -> + leveled_bookie:book_destroy(Pid) + end, + + Wait = wait_for_procs(Procs, 1500), + RunTime = erlang:system_time(millisecond) - StartTime, + + %% Since in parallel commands we don't have access to the state, we retrieve functions + %% from the features + FoldAccTs = [ FoldAccT || Entry <- history(RunResult), + {foldAccT, FoldAccT} <- eqc_statem:history_features(Entry)], + + pretty_commands(?MODULE, Cmds, RunResult, + measure(time_per_test, RunTime, + aggregate(command_names(Cmds), + collect(Kind, + aggregate(with_title('Features'), CallFeatures, + aggregate(with_title('Start Options'), StartOptionFeatures, + features(CallFeatures, + conjunction([{result, + ?WHENFAIL([ begin + eqc:format("~p with acc ~p:\n~s\n", [F, Acc, + show_function(F)]) + end || {F, Acc} <- FoldAccTs ], + result(RunResult) == ok)}, + {data_cleanup, + ?WHENFAIL(eqc:format("~s\n", [os:cmd("ls -Rl " ++ Dir)]), + empty_dir(Dir))}, + {pid_cleanup, equals(Wait, [])}])))))))) + end)) + end))). + +history({H, _, _}) -> H. +result({_, _, Res}) -> Res. + +execute(seq, Cmds, Env) -> + run_commands(Cmds, Env); +execute(par, Cmds, Env) -> + run_parallel_commands(Cmds, Env). + +is_exit({'EXIT', _}) -> + true; +is_exit(Other) -> + {expected_exit, Other}. + +is_foldaccT({foldAccT, _}) -> + true; +is_foldaccT(_) -> + false. + +show_function(F) -> + case proplists:get_value(module, erlang:fun_info(F)) of + eqc_fun -> + eqc_fun:show_function(F); + _ -> + proplists:get_value(name, erlang:fun_info(F)) + end. + + +%% slack discussion: +%% `max_journalsize` should be at least 2048 + byte_size(smallest_object) + byte_size(smallest_object's key) + overhead (which is a few bytes per K/V pair). +gen_opts() -> + options([%% {head_only, elements([false, no_lookup, with_lookup])} we don't test head_only mode + {compression_method, elements([native, lz4])} + , {compression_point, elements([on_compact, on_receipt])} + %% , {max_journalsize, ?LET(N, nat(), 2048 + 1000 + 32 + 16 + 16 + N)} + , {cache_size, oneof([nat(), 2048, 2060, 5000])} + ]). + +options(GenList) -> + ?LET(Bools, vector(length(GenList), bool()), + [ Opt || {Opt, true} <- lists:zip(GenList, Bools)]). + + +gen_key() -> + binary(16). + +%% Cannot be atoms! +%% key() type specified: should be binary(). +gen_bucket() -> + elements([<<"bucket1">>, <<"bucket2">>, <<"bucket3">>]). + +gen_val() -> + noshrink(binary(32)). + +gen_categories(?RIAK_TAG) -> + sublist(categories()); +gen_categories(_) -> + %% for ?STD_TAG this seems to make little sense + []. + + +categories() -> + [dep, lib]. + +gen_category() -> + elements(categories()). + +gen_index_value() -> + %% Carefully selected to have one out-layer and several border cases. + [elements("arts")]. + +gen_key_in_bucket([]) -> + {gen_key(), gen_bucket()}; +gen_key_in_bucket(Previous) -> + ?LET({K, B}, elements(Previous), + frequency([{1, gen_key_in_bucket([])}, + {1, {K, gen_bucket()}}, + {2, {K, B}}])). + +gen_foldacc(2) -> + ?SHRINK(oneof([{eqc_fun:function2(int()), int()}, + {eqc_fun:function2(list(int())), list(int())}]), + [fold_buckets()]); +gen_foldacc(3) -> + ?SHRINK(oneof([{eqc_fun:function3(int()), int()}, + {eqc_fun:function3(list(int())), list(int())}]), + [fold_collect()]); +gen_foldacc(4) -> + ?SHRINK(oneof([{eqc_fun:function4(int()), int()}, + {eqc_fun:function4(list(int())), list(int())}]), + [fold_objects()]). + + + +fold_buckets() -> + {fun(B, Acc) -> [B | Acc] end, []}. + +fold_collect() -> + {fun(X, Y, Acc) -> [{X, Y} | Acc] end, []}. + +fold_objects() -> + {fun(X, Y, Z, Acc) -> [{X, Y, Z} | Acc] end, []}. + +%% This makes system fall over +fold_collect_no_acc() -> + fun(X, Y, Z) -> [{X, Y} | Z] end. + +fold_count() -> + {fun(_X, _Y, Z) -> Z + 1 end, 0}. + +fold_keys() -> + {fun(X, _Y, Z) -> [X | Z] end, []}. + + +empty_dir(Dir) -> + case file:list_dir(Dir) of + {error, enoent} -> true; + {ok, Ds} -> + lists:all(fun(D) -> empty_dir(filename:join(Dir, D)) end, Ds); + _ -> + false + end. + +get_foldobj([], _Counter) -> + undefined; +get_foldobj([#{counter := Counter} = Map | _Rest], Counter) -> + Map; +get_foldobj([_ | Rest], Counter) -> + get_foldobj(Rest, Counter). + + +%% Helper for all those preconditions that just check that leveled Pid +%% is populated in state. (We cannot check with is_pid, since that's +%% symbolic in test case generation!). +is_leveled_open(S) -> + maps:get(leveled, S, undefined) =/= undefined. + +in_head_only_mode(S) -> + proplists:get_value(head_only, maps:get(start_opts, S, []), false) =/= false. + +wait_for_procs(Known, Timeout) -> + case erlang:processes() -- Known of + [] -> []; + Running -> + case Timeout > 0 of + true -> + timer:sleep(100), + wait_for_procs(Known, Timeout - 100); + false -> + Running + end + end. + +delete_level_data(Dir) -> + os:cmd("rm -rf " ++ Dir). + +%% Slack discussion: +%% `[{add, B1, K1, SK1}, {add, B1, K1, SK2}]` should be fine (same bucket and key, different subkey) +no_key_dups([]) -> + []; +no_key_dups([{_Action, Bucket, Key, SubKey, _Value} = E | Es]) -> + [E | no_key_dups([ {A, B, K, SK, V} || {A, B, K, SK, V} <- Es, + {B, K, SK} =/= {Bucket, Key, SubKey}])]. \ No newline at end of file From a13a6ae45fac09167fc32250018e29c96fa9ea34 Mon Sep 17 00:00:00 2001 From: Martin Sumner Date: Tue, 22 Jan 2019 12:53:31 +0000 Subject: [PATCH 2/3] Updated model This has inappropriate default parameter changes. --- .gitignore | 3 + current_counterexample.eqc | Bin 1034 -> 217582 bytes src/leveled_bookie.erl | 16 ++- src/leveled_iclerk.erl | 1 + src/leveled_inker.erl | 4 +- src/leveled_sst.erl | 2 +- test/leveledjc_eqc.erl | 228 ++++++++++++++++++++++++++++++++++--- 7 files changed, 231 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 8031a86..b266ee3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ .DS_Store rebar.lock test/test_area/* +cover +cover_* +.eqc-info diff --git a/current_counterexample.eqc b/current_counterexample.eqc index 775fd177f75722d4ee2fa79f960f1545d15c6e25..c4c9f59c315444d99fded54af30aa076bddbfa3e 100644 GIT binary patch literal 217582 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 diff --git a/src/leveled_bookie.erl b/src/leveled_bookie.erl index 73c0c33..cfa4084 100644 --- a/src/leveled_bookie.erl +++ b/src/leveled_bookie.erl @@ -62,6 +62,7 @@ book_headonly/4, book_snapshot/4, book_compactjournal/2, + book_eqccompactjournal/2, book_islastcompactionpending/1, book_trimjournal/1, book_hotbackup/1, @@ -100,8 +101,8 @@ -include_lib("eunit/include/eunit.hrl"). -define(CACHE_SIZE, 2500). --define(MIN_CACHE_SIZE, 100). --define(MIN_PCL_CACHE_SIZE, 400). +-define(MIN_CACHE_SIZE, 1). +-define(MIN_PCL_CACHE_SIZE, 4). -define(MAX_PCL_CACHE_SIZE, 28000). % This is less than actual max - but COIN_SIDECOUNT -define(CACHE_SIZE_JITTER, 25). @@ -1005,6 +1006,7 @@ book_snapshot(Pid, SnapType, Query, LongRunning) -> -spec book_compactjournal(pid(), integer()) -> ok. +-spec book_eqccompactjournal(pid(), integer()) -> {ok, pid()}. -spec book_islastcompactionpending(pid()) -> boolean(). -spec book_trimjournal(pid()) -> ok. @@ -1013,9 +1015,13 @@ book_snapshot(Pid, SnapType, Query, LongRunning) -> %% the scheduling of Journla compaction is called externally, so it is assumed %% in Riak it will be triggered by a vnode callback. -book_compactjournal(Pid, Timeout) -> +book_eqccompactjournal(Pid, Timeout) -> gen_server:call(Pid, {compact_journal, Timeout}, infinity). +book_compactjournal(Pid, Timeout) -> + {ok, _P} = gen_server:call(Pid, {compact_journal, Timeout}, infinity), + ok. + %% @doc Check on progress of the last compaction book_islastcompactionpending(Pid) -> @@ -1371,10 +1377,10 @@ handle_call({return_runner, QueryType}, _From, State) -> fold_countdown = CountDown}}; handle_call({compact_journal, Timeout}, _From, State) when State#state.head_only == false -> - ok = leveled_inker:ink_compactjournal(State#state.inker, + R = leveled_inker:ink_compactjournal(State#state.inker, self(), Timeout), - {reply, ok, State}; + {reply, R, State}; handle_call(confirm_compact, _From, State) when State#state.head_only == false -> {reply, leveled_inker:ink_compactionpending(State#state.inker), State}; diff --git a/src/leveled_iclerk.erl b/src/leveled_iclerk.erl index 5f6b09a..f0a2318 100644 --- a/src/leveled_iclerk.erl +++ b/src/leveled_iclerk.erl @@ -182,6 +182,7 @@ clerk_hashtablecalc(HashTree, StartPos, CDBpid) -> %% @doc %% Stop the clerk clerk_stop(Pid) -> + unlink(Pid), gen_server:cast(Pid, stop). -spec clerk_loglevel(pid(), leveled_log:log_level()) -> ok. diff --git a/src/leveled_inker.erl b/src/leveled_inker.erl index 3f27e31..cee237a 100644 --- a/src/leveled_inker.erl +++ b/src/leveled_inker.erl @@ -348,7 +348,7 @@ ink_loadpcl(Pid, MinSQN, FilterFun, Penciller) -> as_ink}, infinity). --spec ink_compactjournal(pid(), pid(), integer()) -> ok. +-spec ink_compactjournal(pid(), pid(), integer()) -> {ok, pid()}. %% @doc %% Trigger a compaction event. the compaction event will use a sqn check %% against the Ledger to see if a value can be compacted - if the penciller @@ -612,7 +612,7 @@ handle_call({compact, FilterFun, self(), Timeout), - {reply, ok, State#state{compaction_pending=true}}; + {reply, {ok, State#state.clerk}, State#state{compaction_pending=true}}; handle_call(compaction_complete, _From, State) -> {reply, ok, State#state{compaction_pending=false}}; handle_call(compaction_pending, _From, State) -> diff --git a/src/leveled_sst.erl b/src/leveled_sst.erl index d86f9c5..efe4441 100644 --- a/src/leveled_sst.erl +++ b/src/leveled_sst.erl @@ -72,7 +72,7 @@ -include("include/leveled.hrl"). --define(MAX_SLOTS, 256). +-define(MAX_SLOTS, 2). -define(LOOK_SLOTSIZE, 128). % Maximum of 128 -define(LOOK_BLOCKSIZE, {24, 32}). % 4x + y = ?LOOK_SLOTSIZE -define(NOLOOK_SLOTSIZE, 256). diff --git a/test/leveledjc_eqc.erl b/test/leveledjc_eqc.erl index eec9a64..18c80c8 100644 --- a/test/leveledjc_eqc.erl +++ b/test/leveledjc_eqc.erl @@ -24,7 +24,8 @@ -include_lib("eunit/include/eunit.hrl"). -include("../include/leveled.hrl"). --compile([export_all, nowarn_export_all]). +-compile([export_all, nowarn_export_all, {nowarn_deprecated_function, + [{gen_fsm, send_event, 2}]}]). -define(NUMTESTS, 1000). -define(QC_OUT(P), @@ -75,7 +76,7 @@ init_backend_args(#{dir := Dir, sut := Name} = S) -> case maps:get(start_opts, S, undefined) of undefined -> [ default(?RIAK_TAG, ?STD_TAG), %% Just test one tag at a time - [{root_path, Dir}, {log_level, error} | gen_opts()], Name ]; + [{root_path, Dir}, {log_level, error}, {cache_size, 10}, {max_pencillercachesize, 40}, {max_journalsize, 20000} | gen_opts()], Name ]; Opts -> %% root_path is part of existing options [ maps:get(tag, S), Opts, Name ] @@ -93,6 +94,7 @@ init_backend_adapt(S, [Tag, Options, Name]) -> %% @doc init_backend - The actual operation %% Start the database and read data from disk init_backend(_Tag, Options, Name) -> + % Options0 = proplists:delete(log_level, Options), case leveled_bookie:book_start(Options) of {ok, Bookie} -> unlink(Bookie), @@ -133,9 +135,10 @@ stop(Pid) -> stop_next(S, _Value, [_Pid]) -> S#{leveled => undefined, - folders => [], - used_folders => [], - stop_folders => maps:get(folders, S, []) ++ maps:get(used_folders, S, [])}. + iclerk => undefined, + folders => [], + used_folders => [], + stop_folders => maps:get(folders, S, []) ++ maps:get(used_folders, S, [])}. stop_post(_S, [Pid], _Res) -> Mon = erlang:monitor(process, Pid), @@ -147,6 +150,75 @@ stop_post(_S, [Pid], _Res) -> end. +%% --- Operation: updateload --- +updateload_pre(S) -> + is_leveled_open(S). + +%% updateload for specific bucket (doesn't overlap with get/put/delete) +updateload_args(#{leveled := Pid, tag := Tag}) -> + ?LET(Categories, gen_categories(Tag), + ?LET({{Key, Bucket}, Value, IndexSpec, MetaData}, + {{gen_key(), <<"LoadBucket">>}, gen_val(), [{add, Cat, gen_index_value()} || Cat <- Categories ], []}, + case Tag of + ?STD_TAG -> [Pid, Bucket, Key, Value, Value, IndexSpec, Tag, MetaData]; + ?RIAK_TAG -> + Obj = testutil:riak_object(Bucket, Key, Value, MetaData), %% this has a side effect inserting a random nr + [Pid, Bucket, Key, Value, Obj, IndexSpec, Tag, MetaData] + end + )). + +updateload_pre(#{leveled := Leveled}, [Pid, _Bucket, _Key, _Value, _Obj, _, _, _]) -> + Pid == Leveled. + +updateload_adapt(#{leveled := Leveled}, [_, Bucket, Key, Value, Obj, Spec, Tag, MetaData]) -> + [ Leveled, Bucket, Key, Value, Obj, Spec, Tag, MetaData ]. + +%% @doc put - The actual operation +updateload(Pid, Bucket, Key, Value, Obj, Spec, Tag, MetaData) -> + Values = + case Tag of + ?STD_TAG -> multiply(100, Value); + ?RIAK_TAG -> + lists:map(fun(V) -> testutil:riak_object(Bucket, Key, V, MetaData) end, + multiply(100, Value)) + end ++ [Obj], + lists:map(fun(V) -> leveled_bookie:book_put(Pid, Bucket, Key, V, Spec, Tag) end, Values). + +multiply(1, _Value) -> + []; +multiply(N, Value) when N > 1 -> + <> = Value, + NewValue = <>, + [NewValue | multiply(N-1, NewValue)]. + +updateload_next(#{model := Model} = S, _V, [_Pid, Bucket, Key, _Value, Obj, Spec, _Tag, _MetaData]) -> + ?CMD_VALID(S, put, + begin + NewSpec = + case orddict:find({Bucket, Key}, Model) of + error -> merge_index_spec([], Spec); + {ok, {_, OldSpec}} -> + merge_index_spec(OldSpec, Spec) + end, + S#{model => orddict:store({Bucket, Key}, {Obj, NewSpec}, Model)} + end, + S). + +updateload_post(S, [_, _, _, _, _, _, _, _], Results) -> + lists:all(fun(Res) -> ?CMD_VALID(S, put, Res == ok, Res == {unsupported_message, put}) end, Results). + +updateload_features(#{previous_keys := PK} = S, [_Pid, Bucket, Key, _Value, _Obj, _, Tag, _], _Res) -> + ?CMD_VALID(S, put, + case + lists:member({Key, Bucket}, PK) of + true -> + [{updateload, update, Tag}]; + false -> + [{updateload, insert, Tag}] + end, + [{updateload, unsupported}]). + + %% --- Operation: put --- put_pre(S) -> is_leveled_open(S). @@ -496,6 +568,113 @@ kill(Pid) -> kill_next(S, Value, [Pid]) -> stop_next(S, Value, [Pid]). + + +%% --- Operation: compactisalive --- + +compactisalive_pre(S) -> + is_leveled_open(S) andalso maps:get(iclerk, S, undefined) /= undefined. + +compactisalive_args(#{iclerk := IClerk}) -> + [IClerk]. + +compactisalive_pre(#{iclerk := Pid}, [IClerk]) -> + Pid == IClerk. + +compactisalive(IClerk) -> + is_process_alive(IClerk). + +compactisalive_post(_S, [_IClerk], Res) -> + Res. + + +%% --- Operation: compacthappened --- + +compacthappened_pre(S) -> + is_leveled_open(S) andalso maps:get(iclerk, S, undefined) /= undefined. + +%% Commenting out args disables the operation +%% compacthappened_args(#{dir := DataDir}) -> +%% [DataDir]. + +compacthappened(DataDir) -> + PostCompact = filename:join(DataDir, "journal/journal_files/post_compact"), + case filelib:is_dir(PostCompact) of + true -> + {ok, Files} = file:list_dir(PostCompact), + Files; + false -> + [] + end. + +ledgerpersisted(DataDir) -> + LedgerPath = filename:join(DataDir, "ledger/ledger_files"), + case filelib:is_dir(LedgerPath) of + true -> + {ok, Files} = file:list_dir(LedgerPath), + Files; + false -> + [] + end. + +journalwritten(DataDir) -> + JournalPath = filename:join(DataDir, "journal/journal_files"), + case filelib:is_dir(JournalPath) of + true -> + {ok, Files} = file:list_dir(JournalPath), + Files; + false -> + [] + end. + +compacthappened_post(_S, [_DataDir], Res) -> + eq(Res, []). + + + +%% --- Operation: compact journal --- + +compact_pre(S) -> + is_leveled_open(S). + +compact_args(#{leveled := Pid}) -> + [Pid, nat()]. + +compact_pre(#{leveled := Leveled}, [Pid, _TS]) -> + Pid == Leveled. + +compact_adapt(#{leveled := Leveled}, [_Pid, TS]) -> + [ Leveled, TS ]. + +compact(Pid, TS) -> + {ok, IClerk} = leveled_bookie:book_eqccompactjournal(Pid, TS), + IClerk. + +compact_next(S, IClerk, [_Pid, _TS]) -> + case maps:get(iclerk, S, undefined) of + undefined -> + S#{iclerk => IClerk}; + _ -> + S + end. + +compact_post(S, [_Pid, _TS], Res) -> + case maps:get(iclerk, S, undefined) of + undefined -> + is_pid(Res); + IClerk -> + IClerk == Res + end. + +compact_features(S, [_Pid, _TS], _Res) -> + case maps:get(iclerk, S, undefined) of + undefined -> + [{compact, fresh}]; + _ -> + [{compact, repeat}] + end. + + %% Testing fold: %% Note async and sync mode! %% see https://github.com/martinsumner/riak_kv/blob/mas-2.2.5-tictactaae/src/riak_kv_leveled_backend.erl#L238-L419 @@ -816,11 +995,14 @@ stop_fold_features(S, [_, _], _) -> end ]. -weight(#{previous_keys := []}, Command) when Command == get; - Command == delete -> +weight(#{previous_keys := []}, get) -> + 1; +weight(#{previous_keys := []}, delete) -> 1; weight(S, C) when C == get; - C == put -> + C == put; + C == delete; + C == updateload -> ?CMD_VALID(S, put, 10, 1); weight(_S, stop) -> 1; @@ -849,7 +1031,8 @@ prop_db() -> eqc:dont_print_counterexample( ?LET(Shrinking, parameter(shrinking, false), ?FORALL({Kind, Cmds}, oneof([{seq, more_commands(20, commands(?MODULE))}, - {par, more_commands(2, parallel_commands(?MODULE))}]), + {par, more_commands(2, parallel_commands(?MODULE))} + ]), begin delete_level_data(Dir), ?IMPLIES(empty_dir(Dir), @@ -867,6 +1050,13 @@ prop_db() -> ], StartOptionFeatures = [ lists:keydelete(root_path, 1, Feature) || {start_options, Feature} <- call_features(history(RunResult)) ], + timer:sleep(1000), + CompactionFiles = compacthappened(Dir), + LedgerFiles = ledgerpersisted(Dir), + JournalFiles = journalwritten(Dir), + io:format("File counts: Compacted ~w Journal ~w Ledger ~w~n", [length(CompactionFiles), length(LedgerFiles), length(JournalFiles)]), + + case whereis(maps:get(sut, initial_state())) of undefined -> delete_level_data(Dir); Pid when is_pid(Pid) -> @@ -885,6 +1075,9 @@ prop_db() -> measure(time_per_test, RunTime, aggregate(command_names(Cmds), collect(Kind, + measure(compaction_files, length(CompactionFiles), + measure(ledger_files, length(LedgerFiles), + measure(journal_files, length(JournalFiles), aggregate(with_title('Features'), CallFeatures, aggregate(with_title('Start Options'), StartOptionFeatures, features(CallFeatures, @@ -897,7 +1090,7 @@ prop_db() -> {data_cleanup, ?WHENFAIL(eqc:format("~s\n", [os:cmd("ls -Rl " ++ Dir)]), empty_dir(Dir))}, - {pid_cleanup, equals(Wait, [])}])))))))) + {pid_cleanup, equals(Wait, [])}]))))))))))) end)) end))). @@ -935,7 +1128,7 @@ gen_opts() -> {compression_method, elements([native, lz4])} , {compression_point, elements([on_compact, on_receipt])} %% , {max_journalsize, ?LET(N, nat(), 2048 + 1000 + 32 + 16 + 16 + N)} - , {cache_size, oneof([nat(), 2048, 2060, 5000])} + %% , {cache_size, oneof([nat(), 2048, 2060, 5000])} ]). options(GenList) -> @@ -952,7 +1145,7 @@ gen_bucket() -> elements([<<"bucket1">>, <<"bucket2">>, <<"bucket3">>]). gen_val() -> - noshrink(binary(32)). + noshrink(binary(256)). gen_categories(?RIAK_TAG) -> sublist(categories()); @@ -1044,11 +1237,16 @@ wait_for_procs(Known, Timeout) -> case erlang:processes() -- Known of [] -> []; Running -> - case Timeout > 0 of - true -> + if + Timeout > 100 -> timer:sleep(100), wait_for_procs(Known, Timeout - 100); - false -> + Timeout >= 0 -> + lists:map(fun(P) -> io:format("********* Sending timeout to ~w *********~n", [P]), gen_fsm:send_event(P, timeout) end, Running), + timer:sleep(100), + wait_for_procs(Known, Timeout - 100); + true -> + lists:foreach(fun(P) -> io:format("Process info : ~w~n", [process_info(P)]) end, Running), Running end end. From 7801f16de9efb6b8fee64531124e905d15cfa4eb Mon Sep 17 00:00:00 2001 From: Martin Sumner Date: Fri, 25 Jan 2019 10:24:47 +0000 Subject: [PATCH 3/3] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b266ee3..ae9a11c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test/test_area/* cover cover_* .eqc-info +leveled_data/*