summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2020-02-12 17:18:24 -0600
committerPaul J. Davis <paul.joseph.davis@gmail.com>2020-02-12 17:19:39 -0600
commitae8d939afaf5be314c4516ff3523d165f617774b (patch)
treeb64eba2d9e4e22f4c66c06c12f5fdf1dc5e8e3c8
parenta472206dfd940cb1c4f40d61384918fe216c8eb7 (diff)
downloadcouchdb-ae8d939afaf5be314c4516ff3523d165f617774b.tar.gz
fixup: add size tests
-rw-r--r--src/fabric/test/fabric2_db_size_tests.erl490
1 files 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 f9d514d5c..e96367027 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