You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2020/02/13 20:13:37 UTC

[couchdb] branch prototype/fdb-layer-get-dbs-info updated: more db size tests

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


The following commit(s) were added to refs/heads/prototype/fdb-layer-get-dbs-info by this push:
     new ec59327  more db size tests
ec59327 is described below

commit ec59327c39cb340420ba997e2d7575959356bf5a
Author: Paul J. Davis <pa...@gmail.com>
AuthorDate: Thu Feb 13 14:16:09 2020 -0600

    more db size tests
---
 src/fabric/test/fabric2_db_size_tests.erl | 545 +++++++++++++++++++++++++-----
 1 file changed, 452 insertions(+), 93 deletions(-)

diff --git a/src/fabric/test/fabric2_db_size_tests.erl b/src/fabric/test/fabric2_db_size_tests.erl
index e963670..3ffca9f 100644
--- a/src/fabric/test/fabric2_db_size_tests.erl
+++ b/src/fabric/test/fabric2_db_size_tests.erl
@@ -22,56 +22,49 @@
 -include("fabric2_test.hrl").
 
 
--define(DIFF(Db, Change, Fun), begin
-        ((fun() ->
-            __Before = db_size(Db),
-            __Result = Fun(),
-            __After = db_size(Db),
-            ?debugFmt("~p - ~p == ~p ?= ~p", [
-                __After,
-                __Before,
-                __After - __Before,
-                Change
-            ]),
-            ?assertEqual(Change, __After - __Before),
-            __Result
-        end)())
-    end
-).
-
-
 db_size_test_() ->
     {
-        "Test document CRUD operations",
+        "Test database size calculations",
         {
             setup,
             fun setup/0,
             fun cleanup/1,
             with([
                 ?TDEF(new_doc),
+                ?TDEF(replicate_new_doc),
                 ?TDEF(edit_doc),
                 ?TDEF(delete_doc),
                 ?TDEF(create_conflict),
                 ?TDEF(replicate_new_winner),
                 ?TDEF(replicate_deep_deleted),
                 ?TDEF(delete_winning_revision),
-                ?TDEF(delete_conflict_revision)
+                ?TDEF(delete_conflict_revision),
+                ?TDEF(replicate_existing_revision),
+                ?TDEF(create_doc_with_attachment),
+                ?TDEF(add_attachment_in_update),
+                ?TDEF(add_second_attachment),
+                ?TDEF(delete_attachment),
+                ?TDEF(delete_one_attachment),
+                ?TDEF(delete_all_attachments),
+                ?TDEF(re_add_attachment),
+                ?TDEF(update_and_remove_attachment),
+                ?TDEF(replicate_new_doc_with_attachment),
+                ?TDEF(replicate_remove_attachment),
+                ?TDEF(replicate_stub_attachment),
+                ?TDEF(replicate_stub_and_new_attachment),
+                ?TDEF(replicate_new_att_to_winner),
+                ?TDEF(replicate_change_att_to_winner),
+                ?TDEF(replicate_rem_att_from_winner),
+                ?TDEF(replicate_stub_to_winner),
+                ?TDEF(replicate_new_att_to_conflict),
+                ?TDEF(replicate_change_att_to_conflict),
+                ?TDEF(replicate_rem_att_from_conflict),
+                ?TDEF(replicate_stub_to_conflict)
             ])
         }
     }.
 
 
-% 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}]),
@@ -84,68 +77,276 @@ cleanup({Db, Ctx}) ->
 
 
 new_doc({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}}
-    ],
-    check(Db, Actions).
+    ]).
+
+
+replicate_new_doc({Db, _}) ->
+    check(Db, [
+        {replicate, #{tgt => rev1}}
+    ]).
 
 
 edit_doc({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}},
         {update, #{src => rev1, tgt => rev2}}
-    ],
-    check(Db, Actions).
+    ]).
 
 
 delete_doc({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}},
         {delete, #{src => rev1, tgt => rev2}}
-    ],
-    check(Db, Actions).
+    ]).
 
 
 create_conflict({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}},
         {replicate, #{tgt => rev2}}
-    ],
-    check(Db, Actions).
+    ]).
 
 
 replicate_new_winner({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}},
         {replicate, #{tgt => rev2, depth => 3}}
-    ],
-    check(Db, Actions).
+    ]).
 
 
 replicate_deep_deleted({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1, depth => 2}},
         {replicate, #{tgt => rev2, depth => 5, deleted => true}}
-    ],
-    check(Db, Actions).
+    ]).
 
 
 delete_winning_revision({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}},
         {replicate, #{tgt => rev2}},
-        {delete, #{src => {min, [rev1, rev2]}, tgt => rev3}}
-    ],
-    check(Db, Actions).
+        {delete, #{src => {winner, [rev1, rev2]}, tgt => rev3}}
+    ]).
 
 
 delete_conflict_revision({Db, _}) ->
-    Actions = [
+    check(Db, [
         {create, #{tgt => rev1}},
         {replicate, #{tgt => rev2}},
-        {delete, #{src => {max, [rev1, rev2]}, tgt => rev3}}
-    ],
-    check(Db, Actions).
+        {delete, #{src => {conflict, [rev1, rev2]}, tgt => rev3}}
+    ]).
+
+
+replicate_existing_revision({Db, _}) ->
+    check(Db, [
+        {create, #{tgt => rev1}},
+        {replicate, #{src => rev1, tgt => rev2, depth => 0}}
+    ]).
+
+
+create_doc_with_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1, atts => [att1]}}
+    ]).
+
+
+add_attachment_in_update({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1}},
+        {update, #{src => rev1, tgt => rev2, atts => [att1]}}
+    ]).
+
+
+add_second_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {update, #{src => rev1, tgt => rev2, atts => [att1, att2]}}
+    ]).
+
+
+delete_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {update, #{src => rev1, tgt => rev2}}
+    ]).
+
+
+delete_one_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2}},
+        {mk_att, #{tgt => att3, stub => att1, revpos => 1}},
+        {create, #{tgt => rev1, atts => [att1, att2]}},
+        {update, #{src => rev1, tgt => rev2, atts => [att3]}}
+    ]).
+
+
+delete_all_attachments({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2}},
+        {create, #{tgt => rev1, atts => [att1, att2]}},
+        {update, #{src => rev1, tgt => rev2, atts => []}}
+    ]).
+
+
+re_add_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {update, #{src => rev1, tgt => rev2}},
+        {update, #{src => rev2, tgt => rev3, atts => [att1]}}
+    ]).
+
+
+update_and_remove_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2}},
+        {mk_att, #{tgt => att3, stub => att1, revpos => 1}},
+        {mk_att, #{tgt => att4}},
+        {create, #{tgt => rev1, atts => [att1, att2]}},
+        {update, #{src => rev1, tgt => rev2, atts => [att3, att4]}}
+    ]).
+
+
+replicate_new_doc_with_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {replicate, #{tgt => rev1, atts => [att1]}}
+    ]).
+
+
+replicate_remove_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{src => rev1, tgt => rev2}}
+    ]).
+
+
+replicate_stub_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2, stub => att1, revpos => 1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{src => rev1, tgt => rev2, atts => [att2]}}
+    ]).
+
+
+replicate_stub_and_new_attachment({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2, stub => att1, revpos => 1}},
+        {mk_att, #{tgt => att3}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{src => rev1, tgt => rev2, atts => [att2, att3]}}
+    ]).
+
+
+replicate_new_att_to_winner({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1}},
+        {replicate, #{tgt => rev2}},
+        {replicate, #{
+            src => {winner, [rev1, rev2]},
+            tgt => rev3,
+            atts => [att1]}
+        }
+    ]).
+
+
+replicate_change_att_to_winner({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{tgt => rev2, atts => [att1]}},
+        {replicate, #{
+            src => {winner, [rev1, rev2]},
+            tgt => rev3,
+            atts => [att2]}
+        }
+    ]).
+
+
+replicate_rem_att_from_winner({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{tgt => rev2, atts => [att1]}},
+        {replicate, #{src => {winner, [rev1, rev2]}, tgt => rev3}}
+    ]).
+
+
+replicate_stub_to_winner({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2, stub => att1, revpos => 1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{tgt => rev2, atts => [att1]}},
+        {replicate, #{
+            src => {winner, [rev1, rev2]},
+            tgt => rev3,
+            atts => [att2]}}
+    ]).
+
+
+replicate_new_att_to_conflict({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1}},
+        {replicate, #{tgt => rev2}},
+        {replicate, #{
+            src => {conflict, [rev1, rev2]},
+            tgt => rev3,
+            atts => [att1]}
+        }
+    ]).
+
+
+replicate_change_att_to_conflict({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{tgt => rev2, atts => [att1]}},
+        {replicate, #{
+            src => {conflict, [rev1, rev2]},
+            tgt => rev3,
+            atts => [att2]}
+        }
+    ]).
+
+
+replicate_rem_att_from_conflict({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{tgt => rev2, atts => [att1]}},
+        {replicate, #{src => {conflict, [rev1, rev2]}, tgt => rev3}}
+    ]).
+
+
+replicate_stub_to_conflict({Db, _}) ->
+    check(Db, [
+        {mk_att, #{tgt => att1}},
+        {mk_att, #{tgt => att2, stub => att1, revpos => 1}},
+        {create, #{tgt => rev1, atts => [att1]}},
+        {replicate, #{tgt => rev2, atts => [att1]}},
+        {replicate, #{
+            src => {conflict, [rev1, rev2]},
+            tgt => rev3,
+            atts => [att2]}}
+    ]).
 
 
 check(Db, Actions) ->
@@ -160,7 +361,9 @@ check(Db, Actions) ->
             create -> create_doc(Db, Opts, StAcc);
             update -> update_doc(Db, Opts, StAcc);
             delete -> delete_doc(Db, Opts, StAcc);
-            replicate -> replicate_doc(Db, Opts, StAcc)
+            replicate -> replicate_doc(Db, Opts, StAcc);
+            mk_att -> make_attachment(Opts, StAcc);
+            log_state -> log_state(Opts, StAcc)
         end
     end, InitSt, Actions).
 
@@ -169,24 +372,36 @@ create_doc(Db, Opts, St) ->
     #{
         doc_id := DocId,
         revs := Revs,
+        atts := Atts,
         size := InitDbSize
     } = St,
 
     ?assert(maps:is_key(tgt, Opts)),
 
     Tgt = maps:get(tgt, Opts),
+    AttKeys = maps:get(atts, Opts, []),
     Depth = maps:get(depth, Opts, 1),
 
     ?assert(not maps:is_key(Tgt, Revs)),
+    lists:foreach(fun(AttKey) ->
+        ?assert(maps:is_key(AttKey, Atts))
+    end, AttKeys),
     ?assert(Depth >= 1),
 
+    AttRecords = lists:map(fun(AttKey) ->
+        maps:get(AttKey, Atts)
+    end, AttKeys),
+
     InitDoc = #doc{id = DocId},
-    FinalDoc = lists:foldl(fun(_, Doc0) ->
+    FinalDoc = lists:foldl(fun(Iter, Doc0) ->
         #doc{
             revs = {_OldStart, OldRevs}
         } = Doc1 = randomize_doc(Doc0),
-        {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, Doc1),
-        Doc1#doc{revs = {Pos, [Rev | OldRevs]}}
+        Doc2 = if Iter < Depth -> Doc1; true ->
+            Doc1#doc{atts = AttRecords}
+        end,
+        {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, Doc2),
+        Doc2#doc{revs = {Pos, [Rev | OldRevs]}}
     end, InitDoc, lists:seq(1, Depth)),
 
     FinalDocSize = fabric2_util:rev_size(FinalDoc),
@@ -194,15 +409,13 @@ create_doc(Db, Opts, St) ->
 
     ?assertEqual(FinalDbSize - InitDbSize, FinalDocSize),
 
-    St#{
-        revs := maps:put(Tgt, FinalDoc, Revs),
-        size := FinalDbSize
-    }.
+    store_rev(Db, St, FinalDbSize, Tgt, FinalDoc).
 
 
 update_doc(Db, Opts, St) ->
     #{
         revs := Revs,
+        atts := Atts,
         size := InitDbSize
     } = St,
 
@@ -211,19 +424,30 @@ update_doc(Db, Opts, St) ->
 
     Src = pick_rev(Revs, maps:get(src, Opts)),
     Tgt = maps:get(tgt, Opts),
+    AttKeys = maps:get(atts, Opts, []),
     Depth = maps:get(depth, Opts, 1),
 
     ?assert(maps:is_key(Src, Revs)),
     ?assert(not maps:is_key(Tgt, Revs)),
+    lists:foreach(fun(AttKey) ->
+        ?assert(maps:is_key(AttKey, Atts))
+    end, AttKeys),
     ?assert(Depth >= 1),
 
+    AttRecords = lists:map(fun(AttKey) ->
+        maps:get(AttKey, Atts)
+    end, AttKeys),
+
     InitDoc = maps:get(Src, Revs),
-    FinalDoc = lists:foldl(fun(_, Doc0) ->
+    FinalDoc = lists:foldl(fun(Iter, Doc0) ->
         #doc{
             revs = {_OldStart, OldRevs}
         } = Doc1 = randomize_doc(Doc0),
-        {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, Doc1),
-        Doc1#doc{revs = {Pos, [Rev | OldRevs]}}
+        Doc2 = if Iter < Depth -> Doc1; true ->
+            Doc1#doc{atts = AttRecords}
+        end,
+        {ok, {Pos, Rev}} = fabric2_db:update_doc(Db, Doc2),
+        Doc2#doc{revs = {Pos, [Rev | OldRevs]}}
     end, InitDoc, lists:seq(1, Depth)),
 
     InitDocSize = fabric2_util:rev_size(InitDoc),
@@ -232,10 +456,7 @@ update_doc(Db, Opts, St) ->
 
     ?assertEqual(FinalDbSize - InitDbSize, FinalDocSize - InitDocSize),
 
-    St#{
-        revs := maps:put(Tgt, FinalDoc, Revs),
-        size := FinalDbSize
-    }.
+    store_rev(Db, St, FinalDbSize, Tgt, FinalDoc).
 
 
 delete_doc(Db, Opts, St) ->
@@ -270,16 +491,14 @@ delete_doc(Db, Opts, St) ->
 
     ?assertEqual(FinalDbSize - InitDbSize, FinalDocSize - InitDocSize),
 
-    St#{
-        revs := maps:put(Tgt, FinalDoc, Revs),
-        size := FinalDbSize
-    }.
+    store_rev(Db, St, FinalDbSize, Tgt, FinalDoc).
 
 
 replicate_doc(Db, Opts, St) ->
     #{
         doc_id := DocId,
         revs := Revs,
+        atts := Atts,
         size := InitDbSize
     } = St,
 
@@ -288,6 +507,7 @@ replicate_doc(Db, Opts, St) ->
     Src = pick_rev(Revs, maps:get(src, Opts, undefined)),
     Tgt = maps:get(tgt, Opts),
     Deleted = maps:get(deleted, Opts, false),
+    AttKeys = maps:get(atts, Opts, []),
     Depth = maps:get(depth, Opts, 1),
 
     if Src == undefined -> ok; true ->
@@ -295,14 +515,17 @@ replicate_doc(Db, Opts, St) ->
     end,
     ?assert(not maps:is_key(Tgt, Revs)),
     ?assert(is_boolean(Deleted)),
-    ?assert(Depth >= 1),
+    lists:foreach(fun(AttKey) ->
+        ?assert(maps:is_key(AttKey, Atts))
+    end, AttKeys),
+    ?assert(Depth >= 0),
 
-    InitDoc1 = maps:get(Src, Revs, #doc{id = DocId}),
-    InitDoc2 = case Deleted of
-        true -> randomize_deleted_doc(InitDoc1);
-        false -> randomize_doc(InitDoc1)
+    if Depth > 0 -> ok; true ->
+        ?assert(length(AttKeys) == 0)
     end,
-    FinalDoc = lists:foldl(fun(_, Doc0) ->
+
+    InitDoc = maps:get(Src, Revs, #doc{id = DocId}),
+    NewRevsDoc = lists:foldl(fun(_, Doc0) ->
         #doc{
             revs = {RevStart, RevIds}
         } = Doc0,
@@ -310,11 +533,22 @@ replicate_doc(Db, Opts, St) ->
         Doc0#doc{
             revs = {RevStart + 1, [NewRev | RevIds]}
         }
-    end, InitDoc2, lists:seq(1, Depth)),
+    end, InitDoc, lists:seq(1, Depth)),
+
+    FinalDoc = if NewRevsDoc == InitDoc -> NewRevsDoc; true ->
+        UpdateDoc = case Deleted of
+            true -> randomize_deleted_doc(NewRevsDoc);
+            false -> randomize_doc(NewRevsDoc)
+        end,
+        AttRecords = lists:map(fun(AttKey) ->
+            maps:get(AttKey, Atts)
+        end, AttKeys),
+        UpdateDoc#doc{atts = AttRecords}
+    end,
 
     {ok, _} = fabric2_db:update_doc(Db, FinalDoc, [replicated_changes]),
 
-    InitDocSize = fabric2_util:rev_size(InitDoc1),
+    InitDocSize = fabric2_util:rev_size(InitDoc),
     FinalDocSize = fabric2_util:rev_size(FinalDoc),
     FinalDbSize = db_size(Db),
 
@@ -324,15 +558,99 @@ replicate_doc(Db, Opts, St) ->
     end,
     ?assertEqual(FinalDbSize - InitDbSize, SizeChange),
 
-    St#{
-        revs := maps:put(Tgt, FinalDoc, Revs),
-        size := FinalDbSize
-    }.
+    store_rev(Db, St, FinalDbSize, Tgt, FinalDoc).
+
+
+make_attachment(Opts, St) ->
+    #{
+        atts := Atts
+    } = St,
+
+    ?assert(maps:is_key(tgt, Opts)),
+
+    Tgt = maps:get(tgt, Opts),
+    Stub = maps:get(stub, Opts, undefined),
+    RevPos = maps:get(revpos, Opts, undefined),
+    NameRaw = maps:get(name, Opts, undefined),
+
+    ?assert(not maps:is_key(Tgt, Atts)),
+    if Stub == undefined -> ok; true ->
+        ?assert(maps:is_key(Stub, Atts))
+    end,
+    ?assert(RevPos == undefined orelse RevPos >= 0),
+
+    Name = if
+        NameRaw == undefined -> undefined;
+        is_atom(NameRaw) -> atom_to_binary(NameRaw, utf8);
+        is_binary(NameRaw) -> NameRaw;
+        is_list(NameRaw) -> list_to_binary(NameRaw)
+    end,
+
+    Att0 = case Stub of
+        undefined ->
+            random_attachment(Name);
+        _ ->
+            SrcAtt = maps:get(Stub, Atts),
+            couch_att:store(data, stub, SrcAtt)
+    end,
+    Att1 = if RevPos == undefined -> Att0; true ->
+        couch_att:store(revpos, RevPos, Att0)
+    end,
+
+    St#{atts := maps:put(Tgt, Att1, Atts)}.
+
+
+log_state(_Opts, St) ->
+    #{
+        doc_id := DocId,
+        revs := Revs,
+        atts := Atts,
+        size := DbSize
+    } = St,
+
+    ?debugFmt("~nDocId: ~p~n", [DocId]),
+    ?debugFmt("Db Size: ~p~n~n", [DbSize]),
+
+    RevKeys = maps:keys(Revs),
+    lists:foreach(fun(RevKey) ->
+        #doc{
+            id = RevDocId,
+            revs = {Pos, [Rev | RestRevs]},
+            body = Body,
+            deleted = Deleted,
+            atts = DocAtts,
+            meta = Meta
+        } = Doc = maps:get(RevKey, Revs),
+        ?debugFmt("Doc: ~p (~p)~n", [RevKey, fabric2_util:rev_size(Doc)]),
+        ?debugFmt("Id: ~p~n", [RevDocId]),
+        ?debugFmt("Rev: ~p ~w~n", [Pos, Rev]),
+        lists:foreach(fun(R) ->
+            ?debugFmt("     ~p~n", [R])
+        end, RestRevs),
+        ?debugFmt("Deleted: ~p~n", [Deleted]),
+        ?debugFmt("Atts:~n", []),
+        lists:foreach(fun(Att) ->
+            ?debugFmt("  ~p~n", [Att])
+        end, DocAtts),
+        ?debugFmt("Body: ~p~n", [Body]),
+        ?debugFmt("Meta: ~p~n", [Meta]),
+        ?debugFmt("~n", [])
+    end, lists:sort(RevKeys)),
+
+    AttKeys = maps:keys(Atts),
+    ?debugFmt("~n~nAtts:~n", []),
+    lists:foreach(fun(AttKey) ->
+        Att = maps:get(AttKey, Atts),
+        ?debugFmt("Att: ~p (~p)~n", [AttKey, couch_att:external_size(Att)]),
+        ?debugFmt("  ~p~n", [Att])
+    end, lists:sort(AttKeys)),
+
+    St.
 
 
 pick_rev(_Revs, Rev) when is_atom(Rev) ->
     Rev;
-pick_rev(Revs, {Op, RevList}) when Op == min; Op == max ->
+pick_rev(Revs, {Op, RevList}) when Op == winner; Op == conflict ->
     ChooseFrom = lists:map(fun(Rev) ->
         #doc{
             revs = {S, [R | _]},
@@ -346,12 +664,31 @@ pick_rev(Revs, {Op, RevList}) when Op == min; Op == max ->
     end, RevList),
     Sorted = fabric2_util:sort_revinfos(ChooseFrom),
     RetRev = case Op of
-        min -> lists:last(Sorted);
-        max -> hd(Sorted)
+        winner -> hd(Sorted);
+        conflict -> choose(tl(Sorted))
     end,
     maps:get(name, RetRev).
 
 
+store_rev(Db, St, DbSize, Tgt, #doc{} = Doc) ->
+    #{
+        revs := Revs
+    } = St,
+    #doc{
+        id = DocId,
+        revs = {Pos, [Rev | _]}
+    } = Doc,
+    RevId = {Pos, Rev},
+    {ok, [{ok, DbDoc}]} = fabric2_db:open_doc_revs(Db, DocId, [RevId], []),
+
+    ?assert(not maps:is_key(Tgt, Revs)),
+
+    St#{
+        revs := maps:put(Tgt, DbDoc, Revs),
+        size := DbSize
+    }.
+
+
 randomize_doc(#doc{} = Doc) ->
     Doc#doc{
         deleted = false,
@@ -376,7 +713,6 @@ db_size(Db) when is_map(Db) ->
     db_size(Info).
 
 
-
 -define(MAX_JSON_ELEMENTS, 5).
 -define(MAX_STRING_LEN, 10).
 -define(MAX_INT, 4294967296).
@@ -434,6 +770,28 @@ random_json_number() ->
 
 
 random_json_string() ->
+    random_string(0, ?MAX_STRING_LEN).
+
+
+random_attachment(undefined) ->
+    random_attachment(random_string(1, 32));
+
+random_attachment(Name) when is_binary(Name) ->
+    Type = random_string(1, 32),
+    Data = random_string(1, 512),
+    Md5 = erlang:md5(Data),
+    couch_att:new([
+        {name, Name},
+        {type, Type},
+        {att_len, size(Data)},
+        {data, Data},
+        {encoding, identity},
+        {md5, Md5},
+        {headers, undefined}
+    ]).
+
+
+random_string(MinLen, MaxLen) ->
     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,
@@ -444,13 +802,14 @@ random_json_string() ->
         $ , ${, $}, $[, $], $", $', $-, $_, $+, $=, $,, $.,
         $\x{1}, $\x{a2}, $\x{20ac}, $\x{10348}
     ],
-    Len = rand:uniform(?MAX_STRING_LEN) - 1,
+    Len = MinLen + rand:uniform(MaxLen - MinLen) - 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