This is an automated email from the ASF dual-hosted git repository. davisp pushed a commit to branch prototype/fdb-layer-get-dbs-info in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit add24979f46ee11857ec775b15b8858233195af8 Author: Paul J. Davis <paul.joseph.da...@gmail.com> AuthorDate: Wed Feb 12 17:18:24 2020 -0600 fixup: add size tests --- src/fabric/test/fabric2_db_size_tests.erl | 490 ++++++++++++++++++++++-------- 1 file changed, 356 insertions(+), 134 deletions(-) diff --git a/src/fabric/test/fabric2_db_size_tests.erl b/src/fabric/test/fabric2_db_size_tests.erl index f9d514d..e963670 100644 --- a/src/fabric/test/fabric2_db_size_tests.erl +++ b/src/fabric/test/fabric2_db_size_tests.erl @@ -12,6 +12,9 @@ -module(fabric2_db_size_tests). +-export([ + random_body/0 +]). -include_lib("couch/include/couch_db.hrl"). -include_lib("couch/include/couch_eunit.hrl"). @@ -45,18 +48,30 @@ db_size_test_() -> fun setup/0, fun cleanup/1, with([ - ?TDEF(empty_size), ?TDEF(new_doc), ?TDEF(edit_doc), - ?TDEF(del_doc), - ?TDEF(conflicted_doc), - ?TDEF(del_winner), - ?TDEF(del_conflict) + ?TDEF(delete_doc), + ?TDEF(create_conflict), + ?TDEF(replicate_new_winner), + ?TDEF(replicate_deep_deleted), + ?TDEF(delete_winning_revision), + ?TDEF(delete_conflict_revision) ]) } }. +% TODO: +% replicate existing revision +% replicate with attachment +% replicate removing attachment +% replicate reusing attachment +% replicate adding attachment with stub +% for each, replicate to winner vs non-winner +% for each, replicate extending winner, vs extending conflict vs new branch + + + setup() -> Ctx = test_util:start_couch([fabric]), {ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]), @@ -68,161 +83,288 @@ cleanup({Db, Ctx}) -> test_util:stop_couch(Ctx). -empty_size({Db, _}) -> - ?assertEqual(2, db_size(Db)). - - new_doc({Db, _}) -> - % UUID doc id: 32 - % Revision: 2 + 16 - % Deleted: 1 - % Body: {} = 2 - ?DIFF(Db, 53, fun() -> - create_doc(Db) - end). + Actions = [ + {create, #{tgt => rev1}} + ], + check(Db, Actions). edit_doc({Db, _}) -> - DocId = fabric2_util:uuid(), - {ok, RevId1} = ?DIFF(Db, 53, fun() -> - create_doc(Db, DocId) - end), - % {} -> {"foo":"bar"} = 13 - 2 - {ok, RevId2} = ?DIFF(Db, 11, fun() -> - update_doc(Db, DocId, RevId1, {[{<<"foo">>, <<"bar">>}]}) - end), - ?DIFF(Db, -11, fun() -> - update_doc(Db, DocId, RevId2) - end). - - -del_doc({Db, _}) -> - DocId = fabric2_util:uuid(), - {ok, RevId} = ?DIFF(Db, 64, fun() -> - create_doc(Db, DocId, {[{<<"foo">>, <<"bar">>}]}) - end), - % The change here is -11 becuase we're going from - % {"foo":"bar"} == 13 bytes to {} == 2 bytes. - % I.e., 2 - 13 == -11 - ?DIFF(Db, -11, fun() -> - delete_doc(Db, DocId, RevId) - end). - - -% need to check both new conflict is new winner -% and that new conflict is not a winner and that -% the sizes don't interfere which should be doable -% with different sized bodies. - -conflicted_doc({Db, _}) -> - DocId = fabric2_util:uuid(), - {ok, RevId1} = ?DIFF(Db, 64, fun() -> - create_doc(Db, DocId, {[{<<"foo">>, <<"bar">>}]}) - end), - ?DIFF(Db, 64, fun() -> - create_conflict(Db, DocId, RevId1, {[{<<"foo">>, <<"bar">>}]}) - end). - - -del_winner({Db, _}) -> - DocId = fabric2_util:uuid(), - {ok, RevId1} = ?DIFF(Db, 64, fun() -> - create_doc(Db, DocId, {[{<<"foo">>, <<"bar">>}]}) - end), - {ok, RevId2} = ?DIFF(Db, 64, fun() -> - create_conflict(Db, DocId, RevId1, {[{<<"foo">>, <<"bar">>}]}) - end), - [_ConflictRev, WinnerRev] = lists:sort([RevId1, RevId2]), - ?DIFF(Db, -11, fun() -> - {ok, _RevId3} = delete_doc(Db, DocId, WinnerRev), - ?debugFmt("~n~w~n~w~n~w~n", [RevId1, RevId2, _RevId3]) - end). - - -del_conflict({Db, _}) -> - DocId = fabric2_util:uuid(), - {ok, RevId1} = ?DIFF(Db, 64, fun() -> - create_doc(Db, DocId, {[{<<"foo">>, <<"bar">>}]}) - end), - {ok, RevId2} = ?DIFF(Db, 64, fun() -> - create_conflict(Db, DocId, RevId1, {[{<<"foo">>, <<"bar">>}]}) - end), - [ConflictRev, _WinnerRev] = lists:sort([RevId1, RevId2]), - ?DIFF(Db, -11, fun() -> - {ok, _RevId3} = delete_doc(Db, DocId, ConflictRev), - ?debugFmt("~n~w~n~w~n~w~n", [RevId1, RevId2, _RevId3]) - end). + Actions = [ + {create, #{tgt => rev1}}, + {update, #{src => rev1, tgt => rev2}} + ], + check(Db, Actions). + + +delete_doc({Db, _}) -> + Actions = [ + {create, #{tgt => rev1}}, + {delete, #{src => rev1, tgt => rev2}} + ], + check(Db, Actions). + + +create_conflict({Db, _}) -> + Actions = [ + {create, #{tgt => rev1}}, + {replicate, #{tgt => rev2}} + ], + check(Db, Actions). + + +replicate_new_winner({Db, _}) -> + Actions = [ + {create, #{tgt => rev1}}, + {replicate, #{tgt => rev2, depth => 3}} + ], + check(Db, Actions). + + +replicate_deep_deleted({Db, _}) -> + Actions = [ + {create, #{tgt => rev1, depth => 2}}, + {replicate, #{tgt => rev2, depth => 5, deleted => true}} + ], + check(Db, Actions). + + +delete_winning_revision({Db, _}) -> + Actions = [ + {create, #{tgt => rev1}}, + {replicate, #{tgt => rev2}}, + {delete, #{src => {min, [rev1, rev2]}, tgt => rev3}} + ], + check(Db, Actions). + + +delete_conflict_revision({Db, _}) -> + Actions = [ + {create, #{tgt => rev1}}, + {replicate, #{tgt => rev2}}, + {delete, #{src => {max, [rev1, rev2]}, tgt => rev3}} + ], + check(Db, Actions). + + +check(Db, Actions) -> + InitSt = #{ + doc_id => couch_uuids:random(), + revs => #{}, + atts => #{}, + size => db_size(Db) + }, + lists:foldl(fun({Action, Opts}, StAcc) -> + case Action of + create -> create_doc(Db, Opts, StAcc); + update -> update_doc(Db, Opts, StAcc); + delete -> delete_doc(Db, Opts, StAcc); + replicate -> replicate_doc(Db, Opts, StAcc) + end + end, InitSt, Actions). + + +create_doc(Db, Opts, St) -> + #{ + doc_id := DocId, + revs := Revs, + size := InitDbSize + } = St, + + ?assert(maps:is_key(tgt, Opts)), + + Tgt = maps:get(tgt, Opts), + Depth = maps:get(depth, Opts, 1), + + ?assert(not maps:is_key(Tgt, Revs)), + ?assert(Depth >= 1), + + InitDoc = #doc{id = DocId}, + FinalDoc = lists:foldl(fun(_, Doc0) -> + #doc{ + revs = {_OldStart, OldRevs} + } = Doc1 = randomize_doc(Doc0), + {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, Doc1), + Doc1#doc{revs = {Pos, [Rev | OldRevs]}} + end, InitDoc, lists:seq(1, Depth)), + + FinalDocSize = fabric2_util:rev_size(FinalDoc), + FinalDbSize = db_size(Db), + + ?assertEqual(FinalDbSize - InitDbSize, FinalDocSize), + + St#{ + revs := maps:put(Tgt, FinalDoc, Revs), + size := FinalDbSize + }. -% replicate with attachment -% replicate removing attachment -% replicate reusing attachment -% replicate adding attachment with stub -% for each, replicate to winner vs non-winner -% for each, replicate extending winner, vs extending conflict vs new branch +update_doc(Db, Opts, St) -> + #{ + revs := Revs, + size := InitDbSize + } = St, + ?assert(maps:is_key(src, Opts)), + ?assert(maps:is_key(tgt, Opts)), + Src = pick_rev(Revs, maps:get(src, Opts)), + Tgt = maps:get(tgt, Opts), + Depth = maps:get(depth, Opts, 1), -create_doc(Db) -> - create_doc(Db, fabric2_util:uuid()). + ?assert(maps:is_key(Src, Revs)), + ?assert(not maps:is_key(Tgt, Revs)), + ?assert(Depth >= 1), + InitDoc = maps:get(Src, Revs), + FinalDoc = lists:foldl(fun(_, Doc0) -> + #doc{ + revs = {_OldStart, OldRevs} + } = Doc1 = randomize_doc(Doc0), + {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, Doc1), + Doc1#doc{revs = {Pos, [Rev | OldRevs]}} + end, InitDoc, lists:seq(1, Depth)), -create_doc(Db, DocId) when is_binary(DocId) -> - create_doc(Db, DocId, {[]}); -create_doc(Db, {Props} = Body) when is_list(Props) -> - create_doc(Db, fabric2_util:uuid(), Body). + InitDocSize = fabric2_util:rev_size(InitDoc), + FinalDocSize = fabric2_util:rev_size(FinalDoc), + FinalDbSize = db_size(Db), + ?assertEqual(FinalDbSize - InitDbSize, FinalDocSize - InitDocSize), -create_doc(Db, DocId, Body) -> - Doc = #doc{ - id = DocId, - body = Body - }, - fabric2_db:update_doc(Db, Doc). + St#{ + revs := maps:put(Tgt, FinalDoc, Revs), + size := FinalDbSize + }. -create_conflict(Db, DocId, RevId) -> - create_conflict(Db, DocId, RevId, {[]}). +delete_doc(Db, Opts, St) -> + #{ + revs := Revs, + size := InitDbSize + } = St, + ?assert(maps:is_key(src, Opts)), + ?assert(maps:is_key(tgt, Opts)), -create_conflict(Db, DocId, RevId, Body) -> - {Pos, _} = RevId, - % Only keep the first 16 bytes of the UUID - % so that we match the normal sized revs - <<NewRev:16/binary, _/binary>> = fabric2_util:uuid(), - Doc = #doc{ - id = DocId, - revs = {Pos, [NewRev]}, - body = Body - }, - fabric2_db:update_doc(Db, Doc, [replicated_changes]). + Src = pick_rev(Revs, maps:get(src, Opts)), + Tgt = maps:get(tgt, Opts), + ?assert(maps:is_key(Src, Revs)), + ?assert(not maps:is_key(Tgt, Revs)), -update_doc(Db, DocId, RevId) -> - update_doc(Db, DocId, RevId, {[]}). + InitDoc = maps:get(Src, Revs), + #doc{ + revs = {_OldStart, OldRevs} + } = UpdateDoc = randomize_deleted_doc(InitDoc), + {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, UpdateDoc), -update_doc(Db, DocId, {Pos, Rev}, Body) -> - Doc = #doc{ - id = DocId, - revs = {Pos, [Rev]}, - body = Body + FinalDoc = UpdateDoc#doc{ + revs = {Pos, [Rev | OldRevs]} }, - fabric2_db:update_doc(Db, Doc). + InitDocSize = fabric2_util:rev_size(InitDoc), + FinalDocSize = fabric2_util:rev_size(FinalDoc), + FinalDbSize = db_size(Db), -delete_doc(Db, DocId, RevId) -> - delete_doc(Db, DocId, RevId, {[]}). + ?assertEqual(FinalDbSize - InitDbSize, FinalDocSize - InitDocSize), + St#{ + revs := maps:put(Tgt, FinalDoc, Revs), + size := FinalDbSize + }. -delete_doc(Db, DocId, {Pos, Rev}, Body) -> - Doc = #doc{ - id = DocId, - revs = {Pos, [Rev]}, - deleted = true, - body = Body - }, - fabric2_db:update_doc(Db, Doc). + +replicate_doc(Db, Opts, St) -> + #{ + doc_id := DocId, + revs := Revs, + size := InitDbSize + } = St, + + ?assert(maps:is_key(tgt, Opts)), + + Src = pick_rev(Revs, maps:get(src, Opts, undefined)), + Tgt = maps:get(tgt, Opts), + Deleted = maps:get(deleted, Opts, false), + Depth = maps:get(depth, Opts, 1), + + if Src == undefined -> ok; true -> + ?assert(maps:is_key(Src, Revs)) + end, + ?assert(not maps:is_key(Tgt, Revs)), + ?assert(is_boolean(Deleted)), + ?assert(Depth >= 1), + + InitDoc1 = maps:get(Src, Revs, #doc{id = DocId}), + InitDoc2 = case Deleted of + true -> randomize_deleted_doc(InitDoc1); + false -> randomize_doc(InitDoc1) + end, + FinalDoc = lists:foldl(fun(_, Doc0) -> + #doc{ + revs = {RevStart, RevIds} + } = Doc0, + NewRev = crypto:strong_rand_bytes(16), + Doc0#doc{ + revs = {RevStart + 1, [NewRev | RevIds]} + } + end, InitDoc2, lists:seq(1, Depth)), + + {ok, _} = fabric2_db:update_doc(Db, FinalDoc, [replicated_changes]), + + InitDocSize = fabric2_util:rev_size(InitDoc1), + FinalDocSize = fabric2_util:rev_size(FinalDoc), + FinalDbSize = db_size(Db), + + SizeChange = case Src of + undefined -> FinalDocSize; + _ -> FinalDocSize - InitDocSize + end, + ?assertEqual(FinalDbSize - InitDbSize, SizeChange), + + St#{ + revs := maps:put(Tgt, FinalDoc, Revs), + size := FinalDbSize + }. + + +pick_rev(_Revs, Rev) when is_atom(Rev) -> + Rev; +pick_rev(Revs, {Op, RevList}) when Op == min; Op == max -> + ChooseFrom = lists:map(fun(Rev) -> + #doc{ + revs = {S, [R | _]}, + deleted = Deleted + } = maps:get(Rev, Revs), + #{ + deleted => Deleted, + rev_id => {S, R}, + name => Rev + } + end, RevList), + Sorted = fabric2_util:sort_revinfos(ChooseFrom), + RetRev = case Op of + min -> lists:last(Sorted); + max -> hd(Sorted) + end, + maps:get(name, RetRev). + + +randomize_doc(#doc{} = Doc) -> + Doc#doc{ + deleted = false, + body = random_body() + }. + + +randomize_deleted_doc(Doc) -> + NewDoc = case rand:uniform() < 0.05 of + true -> randomize_doc(Doc); + false -> Doc#doc{body = {[]}} + end, + NewDoc#doc{deleted = true}. db_size(Info) when is_list(Info) -> @@ -232,3 +374,83 @@ db_size(Info) when is_list(Info) -> db_size(Db) when is_map(Db) -> {ok, Info} = fabric2_db:get_db_info(Db), db_size(Info). + + + +-define(MAX_JSON_ELEMENTS, 5). +-define(MAX_STRING_LEN, 10). +-define(MAX_INT, 4294967296). + + +random_body() -> + Elems = rand:uniform(?MAX_JSON_ELEMENTS), + {Obj, _} = random_json_object(Elems), + Obj. + + +random_json(MaxElems) -> + case choose([object, array, terminal]) of + object -> random_json_object(MaxElems); + array -> random_json_array(MaxElems); + terminal -> {random_json_terminal(), MaxElems} + end. + + +random_json_object(MaxElems) -> + NumKeys = rand:uniform(MaxElems + 1) - 1, + {Props, RemElems} = lists:mapfoldl(fun(_, Acc1) -> + {Value, Acc2} = random_json(Acc1), + {{random_json_string(), Value}, Acc2} + end, MaxElems - NumKeys, lists:seq(1, NumKeys)), + {{Props}, RemElems}. + + +random_json_array(MaxElems) -> + NumItems = rand:uniform(MaxElems + 1) - 1, + lists:mapfoldl(fun(_, Acc1) -> + random_json(Acc1) + end, MaxElems - NumItems, lists:seq(1, NumItems)). + + +random_json_terminal() -> + case choose([null, true, false, number, string]) of + null -> null; + true -> true; + false -> false; + number -> random_json_number(); + string -> random_json_string() + end. + + +random_json_number() -> + AbsValue = case choose([integer, double]) of + integer -> rand:uniform(?MAX_INT); + double -> rand:uniform() * rand:uniform() + end, + case choose([pos, neg]) of + pos -> AbsValue; + neg -> -1 * AbsValue + end. + + +random_json_string() -> + Alphabet = [ + $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, + $n, $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z, + $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, + $N, $O, $P, $Q, $R, $S, $T, $U, $V, $W, $Y, $X, $Z, + $1, $2, $3, $4, $5, $6, $7, $8, $9, $0, + $!, $@, $#, $$, $%, $^, $&, $*, $(, $), + $ , ${, $}, $[, $], $", $', $-, $_, $+, $=, $,, $., + $\x{1}, $\x{a2}, $\x{20ac}, $\x{10348} + ], + Len = rand:uniform(?MAX_STRING_LEN) - 1, + Str = lists:map(fun(_) -> + choose(Alphabet) + end, lists:seq(1, Len)), + unicode:characters_to_binary(Str). + + +choose(Options) -> + Pos = rand:uniform(length(Options)), + lists:nth(Pos, Options). \ No newline at end of file