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

Reply via email to