summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2018-05-04 13:10:52 -0500
committerPaul J. Davis <paul.joseph.davis@gmail.com>2018-05-04 13:10:52 -0500
commit8f54e785c3db9d019d1b7d782316b9923a2570e7 (patch)
treec84358ea606bcc250cb241ed226214b118c8e839
parent80fe295dcb29e0b23688acb6c9e65acd65a95321 (diff)
downloadcouchdb-8f54e785c3db9d019d1b7d782316b9923a2570e7.tar.gz
squerge - new PSE test suite
-rw-r--r--src/couch/test/couch_db_purge_docs_tests.erl746
-rw-r--r--src/couch_db_engine_tests/src/cet_test_purge_bad_checkpoints.erl121
-rw-r--r--src/couch_db_engine_tests/src/cet_test_purge_docs.erl575
-rw-r--r--src/couch_db_engine_tests/src/cet_test_purge_replication.erl190
-rw-r--r--src/couch_db_engine_tests/src/cet_util.erl142
5 files changed, 784 insertions, 990 deletions
diff --git a/src/couch/test/couch_db_purge_docs_tests.erl b/src/couch/test/couch_db_purge_docs_tests.erl
deleted file mode 100644
index 1ac42a64c..000000000
--- a/src/couch/test/couch_db_purge_docs_tests.erl
+++ /dev/null
@@ -1,746 +0,0 @@
-% Licensed 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(couch_db_purge_docs_tests).
-
--include_lib("couch/include/couch_eunit.hrl").
--include_lib("couch/include/couch_db.hrl").
-
-
--define(REV_DEPTH, 100).
-
-
-couch_db_purge_docs_test_() ->
- {
- "Couch_db purge_docs",
- [
- {
- setup,
- fun test_util:start_couch/0,
- fun test_util:stop_couch/1,
- [
- couch_db_purge_docs()
- ]
- },
- {
- setup,
- fun start_with_replicator/0,
- fun test_util:stop_couch/1,
- [
- couch_db_purge_with_replication()
- ]
- }
- ]
- }.
-
-
-couch_db_purge_docs() ->
- {
- foreach,
- fun setup/0,
- fun teardown/1,
- [
- fun test_purge_2_to_purge_3/1,
- fun test_purge_all/1,
- fun test_purge_some/1,
- fun test_purge_none/1,
- fun test_purge_missing_docid/1,
- fun test_purge_repeated_docid/1,
- fun test_purge_repeated_rev/1,
- fun test_purge_partial/1,
- fun test_all_removal_purges/1,
- fun test_purge_invalid_rev/1,
- fun test_purge_duplicate_UUID/1,
- fun test_purge_id_not_exist/1,
- fun test_purge_non_leaf_rev/1,
- fun test_purge_deep_tree/1
- ]
- }.
-
-
-couch_db_purge_with_replication() ->
- {
- foreach,
- fun setup_replicator/0,
- fun teardown_replicator/1,
- [
- fun test_purge_with_replication/1
- ]
- }.
-
-
-start_with_replicator() ->
- test_util:start_couch([couch_replicator]).
-
-
-setup() ->
- DbName = ?tempdb(),
- {ok, _Db} = create_db(DbName),
- DbName.
-
-
-teardown(DbName) ->
- delete_db(DbName),
- ok.
-
-
-setup_replicator() ->
- Source = ?tempdb(),
- Target = ?tempdb(),
- {ok, _} = create_db(Source),
- {ok, _} = create_db(Target),
- {Source, Target}.
-
-
-teardown_replicator({Source, Target}) ->
- delete_db(Source),
- delete_db(Target),
- ok.
-
-
-test_purge_2_to_purge_3(DbName) ->
- ?_test(begin
- {ok, Rev} = save_doc(DbName, {[{'_id', foo1}, {vsn, 1.1}]}),
-
- PurgeInfos = [
- {uuid(), <<"foo1">>, [Rev]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
- ?assertEqual([Rev], PRevs),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 0},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_all(DbName) ->
- ?_test(begin
- {ok, [Rev1, Rev2]} = save_docs(DbName, [
- {[{'_id', foo1}, {vsn, 1.1}]},
- {[{'_id', foo2}, {vsn, 1.2}]}
- ]),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 2},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo1">>, [Rev1]},
- {uuid(), <<"foo2">>, [Rev2]}
- ],
-
- {ok, [{ok, PRevs1}, {ok, PRevs2}]} = purge(DbName, PurgeInfos),
-
- ?assertEqual([Rev1], PRevs1),
- ?assertEqual([Rev2], PRevs2),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 0},
- {purge_seq, 2},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_all_removal_purges(DbName) ->
- ?_test(begin
- {ok, Rev1} = save_doc(DbName, {[{'_id', foo}, {vsn, 1}]}),
- Update = {[
- {<<"_id">>, <<"foo">>},
- {<<"_rev">>, couch_doc:rev_to_str(Rev1)},
- {<<"_deleted">>, true},
- {<<"vsn">>, 2}
- ]},
- {ok, Rev2} = save_doc(DbName, Update),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 1},
- {update_seq, 2},
- {changes, 1},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo">>, [Rev2]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
-
- ?assertEqual([Rev2], PRevs),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 0},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_some(DbName) ->
- ?_test(begin
- {ok, [Rev1, _Rev2]} = save_docs(DbName, [
- {[{'_id', foo1}, {vsn, 1}]},
- {[{'_id', foo2}, {vsn, 2}]}
- ]),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 2},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo1">>, [Rev1]}
- ],
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
-
- ?assertEqual([Rev1], PRevs),
-
- assertProps(DbName, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 1},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_none(DbName) ->
- ?_test(begin
- {ok, [_Rev1, _Rev2]} = save_docs(DbName, [
- {[{'_id', foo1}, {vsn, 1}]},
- {[{'_id', foo2}, {vsn, 2}]}
- ]),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 2},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- {ok, []} = purge(DbName, []),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 2},
- {purge_seq, 0},
- {purge_infos, []}
- ])
- end).
-
-
-test_purge_missing_docid(DbName) ->
- ?_test(begin
- {ok, [Rev1, _Rev2]} = save_docs(DbName, [
- {[{'_id', foo1}, {vsn, 1}]},
- {[{'_id', foo2}, {vsn, 2}]}
- ]),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 2},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"baz">>, [Rev1]}
- ],
-
- {ok, [{ok, []}]} = purge(DbName, PurgeInfos),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 2},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_repeated_docid(DbName) ->
- ?_test(begin
- {ok, [Rev1, _Rev2]} = save_docs(DbName, [
- {[{'_id', foo1}, {vsn, 1}]},
- {[{'_id', foo2}, {vsn, 2}]}
- ]),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 2},
- {purge_seq, 0},
- {changes, 2},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo1">>, [Rev1]},
- {uuid(), <<"foo1">>, [Rev1]}
- ],
-
- {ok, Resp} = purge(DbName, PurgeInfos),
- ?assertEqual([{ok, [Rev1]}, {ok, []}], Resp),
-
- assertProps(DbName, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 3},
- {purge_seq, 2},
- {changes, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_id_not_exist(DbName) ->
- ?_test(begin
- PurgeInfos = [
- {uuid(), <<"foo">>, [{0, <<0>>}]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
- ?assertEqual([], PRevs),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 1},
- {changes, 0},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_non_leaf_rev(DbName) ->
- ?_test(begin
- {ok, Rev1} = save_doc(DbName, {[{'_id', foo}, {vsn, 1}]}),
- Update = {[
- {'_id', foo},
- {'_rev', couch_doc:rev_to_str(Rev1)},
- {vsn, 2}
- ]},
- {ok, _Rev2} = save_doc(DbName, Update),
-
- PurgeInfos = [
- {couch_uuids:new(), <<"foo">>, [Rev1]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
- ?assertEqual([], PRevs),
-
- assertProps(DbName, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 1},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_invalid_rev(DbName) ->
- ?_test(begin
- {ok, [_Rev1, Rev2]} = save_docs(DbName, [
- {[{'_id', foo1}, {vsn, 1}]},
- {[{'_id', foo2}, {vsn, 2}]}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo1">>, [Rev2]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
- ?assertEqual([], PRevs),
-
- assertProps(DbName, [
- {doc_count, 2},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 2},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_partial(DbName) ->
- ?_test(begin
- {ok, Rev1} = save_doc(DbName, {[{'_id', foo}, {vsn, <<"v1.1">>}]}),
- Update = {[
- {'_id', foo},
- {'_rev', couch_doc:rev_to_str({1, [crypto:hash(md5, <<"v1.2">>)]})},
- {vsn, <<"v1.2">>}
- ]},
- {ok, [_Rev2]} = save_docs(DbName, [Update], [replicated_changes]),
-
- PurgeInfos = [
- {uuid(), <<"foo">>, [Rev1]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
- ?assertEqual([Rev1], PRevs),
-
- assertProps(DbName, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 1},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_repeated_rev(DbName) ->
- ?_test(begin
- {ok, Rev1} = save_doc(DbName, {[{'_id', foo}, {vsn, <<"v1.1">>}]}),
- Update = {[
- {'_id', foo},
- {'_rev', couch_doc:rev_to_str({1, [crypto:hash(md5, <<"v1.2">>)]})},
- {vsn, <<"v1.2">>}
- ]},
- {ok, [Rev2]} = save_docs(DbName, [Update], [replicated_changes]),
-
- assertProps(DbName, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 1},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos1 = [
- {uuid(), <<"foo">>, [Rev1]},
- {uuid(), <<"foo">>, [Rev1, Rev2]}
- ],
-
- {ok, [{ok, PRevs1}, {ok, PRevs2}]} = purge(DbName, PurgeInfos1),
- ?assertEqual([Rev1], PRevs1),
- ?assertEqual([Rev2], PRevs2),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 0},
- {purge_seq, 2},
- {purge_infos, PurgeInfos1}
- ])
- end).
-
-
-test_purge_deep_tree(DbName) ->
- ?_test(begin
- {ok, InitRev} = save_doc(DbName, {[{'_id', bar}, {vsn, 0}]}),
- LastRev = lists:foldl(fun(Count, PrevRev) ->
- Update = {[
- {'_id', bar},
- {'_rev', couch_doc:rev_to_str(PrevRev)},
- {vsn, Count}
- ]},
- {ok, NewRev} = save_doc(DbName, Update),
- NewRev
- end, InitRev, lists:seq(1, ?REV_DEPTH)),
-
- PurgeInfos = [
- {uuid(), <<"bar">>, [LastRev]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(DbName, PurgeInfos),
- ?assertEqual([LastRev], PRevs),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, ?REV_DEPTH + 2},
- {changes, 0},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_duplicate_UUID(DbName) ->
- ?_test(begin
- {ok, Rev} = save_doc(DbName, {[{'_id', foo1}, {vsn, 1.1}]}),
-
- assertProps(DbName, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 1},
- {changes, 1},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo1">>, [Rev]}
- ],
-
- {ok, [{ok, PRevs1}]} = purge(DbName, PurgeInfos),
- ?assertEqual([Rev], PRevs1),
-
- % Attempting to purge a repeated UUID is an error
- ?assertThrow({badreq, _}, purge(DbName, PurgeInfos)),
-
- % Although we can replicate it in
- {ok, []} = purge(DbName, PurgeInfos, [replicated_changes]),
-
- assertProps(DbName, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 0},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-test_purge_with_replication({Source, Target}) ->
- ?_test(begin
- {ok, Rev1} = save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
-
- assertProps(Source, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 1},
- {changes, 1},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- RepObject = {[
- {<<"source">>, Source},
- {<<"target">>, Target}
- ]},
-
- {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
- {ok, Doc1} = open_doc(Target, foo),
-
- assertProps(Target, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 1},
- {changes, 1},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- PurgeInfos = [
- {uuid(), <<"foo">>, [Rev1]}
- ],
-
- {ok, [{ok, PRevs}]} = purge(Source, PurgeInfos),
- ?assertEqual([Rev1], PRevs),
-
- assertProps(Source, [
- {doc_count, 0},
- {del_doc_count, 0},
- {update_seq, 2},
- {changes, 0},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ]),
-
- % Show that a purge on the source is
- % not replicated to the target
- {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
- {ok, Doc2} = open_doc(Target, foo),
- [Rev2] = Doc2#doc_info.revs,
- ?assertEqual(Rev1, Rev2#rev_info.rev),
- ?assertEqual(Doc1, Doc2),
-
- assertProps(Target, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 1},
- {changes, 1},
- {purge_seq, 0},
- {purge_infos, []}
- ]),
-
- % Show that replicating from the target
- % back to the source reintroduces the doc
- RepObject2 = {[
- {<<"source">>, Target},
- {<<"target">>, Source}
- ]},
-
- {ok, _} = couch_replicator:replicate(RepObject2, ?ADMIN_USER),
- {ok, Doc3} = open_doc(Source, foo),
- [Revs3] = Doc3#doc_info.revs,
- ?assertEqual(Rev1, Revs3#rev_info.rev),
-
- assertProps(Source, [
- {doc_count, 1},
- {del_doc_count, 0},
- {update_seq, 3},
- {changes, 1},
- {purge_seq, 1},
- {purge_infos, PurgeInfos}
- ])
- end).
-
-
-create_db(DbName) ->
- couch_db:create(DbName, [?ADMIN_CTX, overwrite]).
-
-
-delete_db(DbName) ->
- couch_server:delete(DbName, [?ADMIN_CTX]).
-
-
-save_doc(DbName, Json) ->
- {ok, [Rev]} = save_docs(DbName, [Json], []),
- {ok, Rev}.
-
-
-save_docs(DbName, JsonDocs) ->
- save_docs(DbName, JsonDocs, []).
-
-
-save_docs(DbName, JsonDocs, Options) ->
- Docs = lists:map(fun(JDoc) ->
- couch_doc:from_json_obj(?JSON_DECODE(?JSON_ENCODE(JDoc)))
- end, JsonDocs),
- Opts = [full_commit | Options],
- {ok, Db} = couch_db:open_int(DbName, []),
- try
- case lists:member(replicated_changes, Options) of
- true ->
- {ok, []} = couch_db:update_docs(
- Db, Docs, Opts, replicated_changes),
- {ok, lists:map(fun(Doc) ->
- {Pos, [RevId | _]} = Doc#doc.revs,
- {Pos, RevId}
- end, Docs)};
- false ->
- {ok, Resp} = couch_db:update_docs(Db, Docs, Opts),
- {ok, [Rev || {ok, Rev} <- Resp]}
- end
- after
- couch_db:close(Db)
- end.
-
-
-open_doc(DbName, DocId0) ->
- DocId = ?JSON_DECODE(?JSON_ENCODE(DocId0)),
- {ok, Db} = couch_db:open_int(DbName, []),
- try
- couch_db:get_doc_info(Db, DocId)
- after
- couch_db:close(Db)
- end.
-
-
-purge(DbName, PurgeInfos) ->
- purge(DbName, PurgeInfos, []).
-
-
-purge(DbName, PurgeInfos0, Options) when is_list(PurgeInfos0) ->
- PurgeInfos = lists:map(fun({UUID, DocIdJson, Revs}) ->
- {UUID, ?JSON_DECODE(?JSON_ENCODE(DocIdJson)), Revs}
- end, PurgeInfos0),
- {ok, Db} = couch_db:open_int(DbName, []),
- try
- couch_db:purge_docs(Db, PurgeInfos, Options)
- after
- couch_db:close(Db)
- end.
-
-
-assertProps(DbName, Props) when is_binary(DbName) ->
- {ok, Db} = couch_db:open_int(DbName, []),
- try
- assertEachProp(Db, Props)
- after
- couch_db:close(Db)
- end.
-
-
-assertEachProp(_Db, []) ->
- ok;
-assertEachProp(Db, [{doc_count, Expect} | Rest]) ->
- {ok, DocCount} = couch_db:get_doc_count(Db),
- ?assertEqual(Expect, DocCount),
- assertEachProp(Db, Rest);
-assertEachProp(Db, [{del_doc_count, Expect} | Rest]) ->
- {ok, DelDocCount} = couch_db:get_del_doc_count(Db),
- ?assertEqual(Expect, DelDocCount),
- assertEachProp(Db, Rest);
-assertEachProp(Db, [{update_seq, Expect} | Rest]) ->
- UpdateSeq = couch_db:get_update_seq(Db),
- ?assertEqual(Expect, UpdateSeq),
- assertEachProp(Db, Rest);
-assertEachProp(Db, [{changes, Expect} | Rest]) ->
- {ok, NumChanges} = couch_db:fold_changes(Db, 0, fun fold_changes/2, 0, []),
- ?assertEqual(Expect, NumChanges),
- assertEachProp(Db, Rest);
-assertEachProp(Db, [{purge_seq, Expect} | Rest]) ->
- {ok, PurgeSeq} = couch_db:get_purge_seq(Db),
- ?assertEqual(Expect, PurgeSeq),
- assertEachProp(Db, Rest);
-assertEachProp(Db, [{purge_infos, Expect} | Rest]) ->
- {ok, PurgeInfos} = couch_db:fold_purge_infos(Db, 0, fun fold_fun/2, [], []),
- ?assertEqual(Expect, lists:reverse(PurgeInfos)),
- assertEachProp(Db, Rest).
-
-
-fold_fun({_PSeq, UUID, Id, Revs}, Acc) ->
- {ok, [{UUID, Id, Revs} | Acc]}.
-
-
-fold_changes(_A, Acc) ->
- %io:format(standard_error, "~nCHANGE: ~p~n~n", [_A]),
- {ok, Acc + 1}.
-
-
-uuid() -> couch_uuids:random().
diff --git a/src/couch_db_engine_tests/src/cet_test_purge_bad_checkpoints.erl b/src/couch_db_engine_tests/src/cet_test_purge_bad_checkpoints.erl
new file mode 100644
index 000000000..2d53f6108
--- /dev/null
+++ b/src/couch_db_engine_tests/src/cet_test_purge_bad_checkpoints.erl
@@ -0,0 +1,121 @@
+% Licensed 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(cet_test_purge_bad_checkpoints).
+-compile(export_all).
+-compile(nowarn_export_all).
+
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+
+setup_test() ->
+ {ok, Db1} = cet_util:create_db(),
+ {ok, Revs} = cet_util:save_docs(couch_db:name(Db1), [
+ {[{'_id', foo0}, {vsn, 0}]},
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]},
+ {[{'_id', foo3}, {vsn, 3}]},
+ {[{'_id', foo4}, {vsn, 4}]},
+ {[{'_id', foo5}, {vsn, 5}]},
+ {[{'_id', foo6}, {vsn, 6}]},
+ {[{'_id', foo7}, {vsn, 7}]},
+ {[{'_id', foo8}, {vsn, 8}]},
+ {[{'_id', foo9}, {vsn, 9}]}
+ ]),
+ PInfos = lists:map(fun(Idx) ->
+ DocId = iolist_to_binary(["foo", $0 + Idx]),
+ Rev = lists:nth(Idx + 1, Revs),
+ {cet_util:uuid(), DocId, [Rev]}
+ end, lists:seq(0, 9)),
+ {ok, _} = cet_util:purge(couch_db:name(Db1), PInfos),
+ {ok, Db2} = couch_db:reopen(Db1),
+ Db2.
+
+
+teardown_test(Db) ->
+ ok = couch_server:delete(couch_db:name(Db), []).
+
+
+cet_bad_purge_seq(Db1) ->
+ Db2 = save_local_doc(Db1, <<"foo">>, ?MODULE, valid_fun),
+ ?assertEqual(0, couch_db:get_minimum_purge_seq(Db2)),
+
+ ok = couch_db:set_purge_infos_limit(Db2, 5),
+ {ok, Db3} = couch_db:reopen(Db2),
+ ?assertEqual(1, couch_db:get_minimum_purge_seq(Db3)).
+
+
+cet_bad_verify_mod(Db1) ->
+ Db2 = save_local_doc(Db1, 2, [invalid_module], valid_fun),
+ ?assertEqual(0, couch_db:get_minimum_purge_seq(Db2)),
+
+ ok = couch_db:set_purge_infos_limit(Db2, 5),
+ {ok, Db3} = couch_db:reopen(Db2),
+ ?assertEqual(2, couch_db:get_minimum_purge_seq(Db3)).
+
+
+cet_bad_verify_fun(Db1) ->
+ Db2 = save_local_doc(Db1, 2, ?MODULE, [invalid_function]),
+ ?assertEqual(0, couch_db:get_minimum_purge_seq(Db2)),
+
+ ok = couch_db:set_purge_infos_limit(Db2, 5),
+ {ok, Db3} = couch_db:reopen(Db2),
+ ?assertEqual(2, couch_db:get_minimum_purge_seq(Db3)).
+
+
+cet_verify_fun_throws(Db1) ->
+ Db2 = save_local_doc(Db1, 2, ?MODULE, throw_fun),
+ ?assertEqual(0, couch_db:get_minimum_purge_seq(Db2)),
+
+ ok = couch_db:set_purge_infos_limit(Db2, 5),
+ {ok, Db3} = couch_db:reopen(Db2),
+ ?assertEqual(2, couch_db:get_minimum_purge_seq(Db3)).
+
+
+cet_verify_non_boolean(Db1) ->
+ Db2 = save_local_doc(Db1, 2, ?MODULE, non_bool_fun),
+ ?assertEqual(0, couch_db:get_minimum_purge_seq(Db2)),
+
+ ok = couch_db:set_purge_infos_limit(Db2, 5),
+ {ok, Db3} = couch_db:reopen(Db2),
+ ?assertEqual(2, couch_db:get_minimum_purge_seq(Db3)).
+
+
+save_local_doc(Db1, PurgeSeq, Mod, Fun) ->
+ {Mega, Secs, _} = os:timestamp(),
+ NowSecs = Mega * 1000000 + Secs,
+ Doc = couch_doc:from_json_obj(?JSON_DECODE(?JSON_ENCODE({[
+ {<<"_id">>, <<"_local/purge-test-stuff">>},
+ {<<"purge_seq">>, PurgeSeq},
+ {<<"timestamp_utc">>, NowSecs},
+ {<<"verify_module">>, Mod},
+ {<<"verify_function">>, Fun},
+ {<<"verify_options">>, {[{<<"signature">>, <<"stuff">>}]}},
+ {<<"type">>, <<"test">>}
+ ]}))),
+ {ok, _} = couch_db:update_doc(Db1, Doc, []),
+ {ok, Db2} = couch_db:reopen(Db1),
+ Db2.
+
+
+valid_fun(_Options) ->
+ true.
+
+
+throw_fun(_Options) ->
+ throw(failed).
+
+
+not_bool(_Options) ->
+ ok.
diff --git a/src/couch_db_engine_tests/src/cet_test_purge_docs.erl b/src/couch_db_engine_tests/src/cet_test_purge_docs.erl
index 06a38586a..84157be32 100644
--- a/src/couch_db_engine_tests/src/cet_test_purge_docs.erl
+++ b/src/couch_db_engine_tests/src/cet_test_purge_docs.erl
@@ -19,205 +19,446 @@
-include_lib("couch/include/couch_db.hrl").
+-define(REV_DEPTH, 100).
+
+
setup_test() ->
{ok, Db} = cet_util:create_db(),
- Db.
+ couch_db:name(Db).
-teardown_test(Db) ->
- ok = couch_server:delete(couch_db:name(Db), []).
+teardown_test(DbName) ->
+ ok = couch_server:delete(DbName, []).
-cet_purge_simple(Db1) ->
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}}
- ],
- {ok, Db2} = cet_util:apply_actions(Db1, Actions1),
- {ok, PIdRevs2} = couch_db_engine:fold_purge_infos(
- Db2, 0, fun fold_fun/2, [], []),
-
- ?assertEqual(1, couch_db_engine:get_doc_count(Db2)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)),
- ?assertEqual(1, couch_db_engine:get_update_seq(Db2)),
- ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)),
- ?assertEqual([], PIdRevs2),
-
- [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]),
- PrevRev = cet_util:prev_rev(FDI),
- Rev = PrevRev#rev_info.rev,
-
- Actions2 = [
- {purge, {<<"foo">>, Rev}}
+cet_purge_simple(DbName) ->
+ {ok, Rev} = cet_util:save_doc(DbName, {[{'_id', foo1}, {vsn, 1.1}]}),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev]}
],
- {ok, Db3} = cet_util:apply_actions(Db2, Actions2),
- {ok, PIdRevs3} = couch_db_engine:fold_purge_infos(
- Db3, 0, fun fold_fun/2, [], []),
- ?assertEqual(0, couch_db_engine:get_doc_count(Db3)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)),
- ?assertEqual(2, couch_db_engine:get_update_seq(Db3)),
- ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)),
- ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3).
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([Rev], PRevs),
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
-cet_purge_UUID(Db1) ->
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}}
- ],
- {ok, Db2} = cet_util:apply_actions(Db1, Actions1),
- {ok, PIdRevs2} = couch_db_engine:fold_purge_infos(
- Db2, 0, fun fold_fun/2, [], []),
-
- ?assertEqual(1, couch_db_engine:get_doc_count(Db2)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)),
- ?assertEqual(1, couch_db_engine:get_update_seq(Db2)),
- ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)),
- ?assertEqual([], PIdRevs2),
-
- [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]),
- PrevRev = cet_util:prev_rev(FDI),
- Rev = PrevRev#rev_info.rev,
-
- Actions2 = [
- {purge, {<<"foo">>, Rev}}
+
+cet_purge_simple_info_check(DbName) ->
+ {ok, Rev} = cet_util:save_doc(DbName, {[{'_id', foo1}, {vsn, 1.1}]}),
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev]}
],
- {ok, Db3} = cet_util:apply_actions(Db2, Actions2),
- {ok, PIdRevs3} = couch_db_engine:fold_purge_infos(
- Db3, 0, fun fold_fun/2, [], []),
-
- ?assertEqual(0, couch_db_engine:get_doc_count(Db3)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)),
- ?assertEqual(2, couch_db_engine:get_update_seq(Db3)),
- ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)),
- ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3),
-
- {ok, {PSeq, UUID}} = couch_db_engine:fold_purge_infos(
- Db3, 0, fun first_uuid/2, [], []),
- ?assertEqual(1, PSeq),
- ?assert(is_binary(UUID)).
-
-
-cet_purge_conflicts(Db1) ->
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}},
- {conflict, {<<"foo">>, {[{<<"vsn">>, 2}]}}}
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([Rev], PRevs),
+
+ {ok, AllInfos} = couch_util:with_db(DbName, fun(Db) ->
+ couch_db_engine:fold_purge_infos(Db, 0, fun fold_all_infos/2, [], [])
+ end),
+
+ ?assertMatch([{1, <<_/binary>>, <<"foo1">>, [Rev]}], AllInfos).
+
+
+cet_purge_empty_db(DbName) ->
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [{0, <<0>>}]}
],
- {ok, Db2} = cet_util:apply_actions(Db1, Actions1),
- {ok, PIdRevs2} = couch_db_engine:fold_purge_infos(
- Db2, 0, fun fold_fun/2, [], []),
-
- ?assertEqual(1, couch_db_engine:get_doc_count(Db2)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)),
- ?assertEqual(2, couch_db_engine:get_update_seq(Db2)),
- ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)),
- ?assertEqual([], PIdRevs2),
-
- [FDI1] = couch_db_engine:open_docs(Db2, [<<"foo">>]),
- PrevRev1 = cet_util:prev_rev(FDI1),
- Rev1 = PrevRev1#rev_info.rev,
-
- Actions2 = [
- {purge, {<<"foo">>, Rev1}}
+
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([], PRevs),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 1},
+ {changes, 0},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_single_docid(DbName) ->
+ {ok, [Rev1, _Rev2]} = cet_util:save_docs(DbName, [
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]}
+ ]),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 2},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev1]}
],
- {ok, Db3} = cet_util:apply_actions(Db2, Actions2),
- {ok, PIdRevs3} = couch_db_engine:fold_purge_infos(
- Db3, 0, fun fold_fun/2, [], []),
-
- ?assertEqual(1, couch_db_engine:get_doc_count(Db3)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)),
- ?assertEqual(3, couch_db_engine:get_update_seq(Db3)),
- ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)),
- ?assertEqual([{<<"foo">>, [Rev1]}], PIdRevs3),
-
- [FDI2] = couch_db_engine:open_docs(Db3, [<<"foo">>]),
- PrevRev2 = cet_util:prev_rev(FDI2),
- Rev2 = PrevRev2#rev_info.rev,
-
- Actions3 = [
- {purge, {<<"foo">>, Rev2}}
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([Rev1], PRevs),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 1},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_multiple_docids(DbName) ->
+ {ok, [Rev1, Rev2]} = cet_util:save_docs(DbName, [
+ {[{'_id', foo1}, {vsn, 1.1}]},
+ {[{'_id', foo2}, {vsn, 1.2}]}
+ ]),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 2},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev1]},
+ {cet_util:uuid(), <<"foo2">>, [Rev2]}
],
- {ok, Db4} = cet_util:apply_actions(Db3, Actions3),
- {ok, PIdRevs4} = couch_db_engine:fold_purge_infos(
- Db4, 0, fun fold_fun/2, [], []),
-
- ?assertEqual(0, couch_db_engine:get_doc_count(Db4)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db4)),
- ?assertEqual(4, couch_db_engine:get_update_seq(Db4)),
- ?assertEqual(2, couch_db_engine:get_purge_seq(Db4)),
- ?assertEqual([{<<"foo">>, [Rev2]}, {<<"foo">>, [Rev1]}], PIdRevs4).
+ {ok, [{ok, PRevs1}, {ok, PRevs2}]} = cet_util:purge(DbName, PurgeInfos),
+
+ ?assertEqual([Rev1], PRevs1),
+ ?assertEqual([Rev2], PRevs2),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 0},
+ {purge_seq, 2},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_no_docids(DbName) ->
+ {ok, [_Rev1, _Rev2]} = cet_util:save_docs(DbName, [
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]}
+ ]),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 2},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ {ok, []} = cet_util:purge(DbName, []),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 2},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]).
+
+
+cet_purge_rev_path(DbName) ->
+ {ok, Rev1} = cet_util:save_doc(DbName, {[{'_id', foo}, {vsn, 1}]}),
+ Update = {[
+ {<<"_id">>, <<"foo">>},
+ {<<"_rev">>, couch_doc:rev_to_str(Rev1)},
+ {<<"_deleted">>, true},
+ {<<"vsn">>, 2}
+ ]},
+ {ok, Rev2} = cet_util:save_doc(DbName, Update),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 1},
+ {update_seq, 2},
+ {changes, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [Rev2]}
+ ],
-cet_add_delete_purge(Db1) ->
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}},
- {delete, {<<"foo">>, {[{<<"vsn">>, 2}]}}}
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([Rev2], PRevs),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 0},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_deep_revision_path(DbName) ->
+ {ok, InitRev} = cet_util:save_doc(DbName, {[{'_id', bar}, {vsn, 0}]}),
+ LastRev = lists:foldl(fun(Count, PrevRev) ->
+ Update = {[
+ {'_id', bar},
+ {'_rev', couch_doc:rev_to_str(PrevRev)},
+ {vsn, Count}
+ ]},
+ {ok, NewRev} = cet_util:save_doc(DbName, Update),
+ NewRev
+ end, InitRev, lists:seq(1, ?REV_DEPTH)),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"bar">>, [LastRev]}
],
- {ok, Db2} = cet_util:apply_actions(Db1, Actions1),
- {ok, PIdRevs2} = couch_db_engine:fold_purge_infos(
- Db2, 0, fun fold_fun/2, [], []),
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([LastRev], PRevs),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, ?REV_DEPTH + 2},
+ {changes, 0},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_partial_revs(DbName) ->
+ {ok, Rev1} = cet_util:save_doc(DbName, {[{'_id', foo}, {vsn, <<"1.1">>}]}),
+ Update = {[
+ {'_id', foo},
+ {'_rev', couch_doc:rev_to_str({1, [crypto:hash(md5, <<"1.2">>)]})},
+ {vsn, <<"1.2">>}
+ ]},
+ {ok, [_Rev2]} = cet_util:save_docs(DbName, [Update], [replicated_changes]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [Rev1]}
+ ],
- ?assertEqual(0, couch_db_engine:get_doc_count(Db2)),
- ?assertEqual(1, couch_db_engine:get_del_doc_count(Db2)),
- ?assertEqual(2, couch_db_engine:get_update_seq(Db2)),
- ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)),
- ?assertEqual([], PIdRevs2),
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([Rev1], PRevs),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 1},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_missing_docid(DbName) ->
+ {ok, [Rev1, _Rev2]} = cet_util:save_docs(DbName, [
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]}
+ ]),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 2},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"baz">>, [Rev1]}
+ ],
- [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]),
- PrevRev = cet_util:prev_rev(FDI),
- Rev = PrevRev#rev_info.rev,
+ {ok, [{ok, []}]} = cet_util:purge(DbName, PurgeInfos),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 2},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_duplicate_docids(DbName) ->
+ {ok, [Rev1, _Rev2]} = cet_util:save_docs(DbName, [
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]}
+ ]),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {purge_seq, 0},
+ {changes, 2},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev1]},
+ {cet_util:uuid(), <<"foo1">>, [Rev1]}
+ ],
- Actions2 = [
- {purge, {<<"foo">>, Rev}}
+ {ok, Resp} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([{ok, [Rev1]}, {ok, []}], Resp),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {purge_seq, 2},
+ {changes, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_internal_revision(DbName) ->
+ {ok, Rev1} = cet_util:save_doc(DbName, {[{'_id', foo}, {vsn, 1}]}),
+ Update = {[
+ {'_id', foo},
+ {'_rev', couch_doc:rev_to_str(Rev1)},
+ {vsn, 2}
+ ]},
+ {ok, _Rev2} = cet_util:save_doc(DbName, Update),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [Rev1]}
],
- {ok, Db3} = cet_util:apply_actions(Db2, Actions2),
- {ok, PIdRevs3} = couch_db_engine:fold_purge_infos(
- Db3, 0, fun fold_fun/2, [], []),
- ?assertEqual(0, couch_db_engine:get_doc_count(Db3)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)),
- ?assertEqual(3, couch_db_engine:get_update_seq(Db3)),
- ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)),
- ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3).
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([], PRevs),
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 1},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
-cet_add_two_purge_one(Db1) ->
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}},
- {create, {<<"bar">>, {[]}}}
- ],
- {ok, Db2} = cet_util:apply_actions(Db1, Actions1),
- {ok, PIdRevs2} = couch_db_engine:fold_purge_infos(
- Db2, 0, fun fold_fun/2, [], []),
+cet_purge_missing_revision(DbName) ->
+ {ok, [_Rev1, Rev2]} = cet_util:save_docs(DbName, [
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]}
+ ]),
- ?assertEqual(2, couch_db_engine:get_doc_count(Db2)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)),
- ?assertEqual(2, couch_db_engine:get_update_seq(Db2)),
- ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)),
- ?assertEqual([], PIdRevs2),
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev2]}
+ ],
- [FDI] = couch_db_engine:open_docs(Db2, [<<"foo">>]),
- PrevRev = cet_util:prev_rev(FDI),
- Rev = PrevRev#rev_info.rev,
+ {ok, [{ok, PRevs}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([], PRevs),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 2},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 2},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_repeated_revisions(DbName) ->
+ {ok, Rev1} = cet_util:save_doc(DbName, {[{'_id', foo}, {vsn, <<"1.1">>}]}),
+ Update = {[
+ {'_id', foo},
+ {'_rev', couch_doc:rev_to_str({1, [crypto:hash(md5, <<"1.2">>)]})},
+ {vsn, <<"1.2">>}
+ ]},
+ {ok, [Rev2]} = cet_util:save_docs(DbName, [Update], [replicated_changes]),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos1 = [
+ {cet_util:uuid(), <<"foo">>, [Rev1]},
+ {cet_util:uuid(), <<"foo">>, [Rev1, Rev2]}
+ ],
- Actions2 = [
- {purge, {<<"foo">>, Rev}}
+ {ok, [{ok, PRevs1}, {ok, PRevs2}]} = cet_util:purge(DbName, PurgeInfos1),
+ ?assertEqual([Rev1], PRevs1),
+ ?assertEqual([Rev2], PRevs2),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 0},
+ {purge_seq, 2},
+ {purge_infos, PurgeInfos1}
+ ]).
+
+
+cet_purge_repeated_uuid(DbName) ->
+ {ok, Rev} = cet_util:save_doc(DbName, {[{'_id', foo1}, {vsn, 1.1}]}),
+
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 1},
+ {changes, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo1">>, [Rev]}
],
- {ok, Db3} = cet_util:apply_actions(Db2, Actions2),
- {ok, PIdRevs3} = couch_db_engine:fold_purge_infos(
- Db3, 0, fun fold_fun/2, [], []),
- ?assertEqual(1, couch_db_engine:get_doc_count(Db3)),
- ?assertEqual(0, couch_db_engine:get_del_doc_count(Db3)),
- ?assertEqual(3, couch_db_engine:get_update_seq(Db3)),
- ?assertEqual(1, couch_db_engine:get_purge_seq(Db3)),
- ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3).
+ {ok, [{ok, PRevs1}]} = cet_util:purge(DbName, PurgeInfos),
+ ?assertEqual([Rev], PRevs1),
+
+ % Attempting to purge a repeated UUID is an error
+ ?assertThrow({badreq, _}, cet_util:purge(DbName, PurgeInfos)),
+ % Although we can replicate it in
+ {ok, []} = cet_util:purge(DbName, PurgeInfos, [replicated_changes]),
-fold_fun({_Pseq, _UUID, Id, Revs}, Acc) ->
- {ok, [{Id, Revs} | Acc]}.
+ cet_util:assert_db_props(DbName, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 0},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
-first_uuid({PSeq, UUID, _, _}, _) ->
- {stop, {PSeq, UUID}}.
+fold_all_infos(Info, Acc) ->
+ {ok, [Info | Acc]}.
diff --git a/src/couch_db_engine_tests/src/cet_test_purge_replication.erl b/src/couch_db_engine_tests/src/cet_test_purge_replication.erl
index 4f5ab4cb0..e520a2e8f 100644
--- a/src/couch_db_engine_tests/src/cet_test_purge_replication.erl
+++ b/src/couch_db_engine_tests/src/cet_test_purge_replication.erl
@@ -21,7 +21,7 @@
setup_mod() ->
- cet_util:setup_mod([mem3, fabric]).
+ cet_util:setup_mod([mem3, fabric, couch_replicator]).
setup_test() ->
@@ -35,76 +35,159 @@ teardown_test({SrcDb, TgtDb}) ->
ok = couch_server:delete(TgtDb, []).
-cet_purge_repl_disabled({SrcDb, TgtDb}) ->
+cet_purge_http_replication({Source, Target}) ->
+ {ok, Rev1} = cet_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
+
+ cet_util:assert_db_props(Source, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 1},
+ {changes, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ RepObject = {[
+ {<<"source">>, Source},
+ {<<"target">>, Target}
+ ]},
+
+ {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
+ {ok, Doc1} = cet_util:open_doc(Target, foo),
+
+ cet_util:assert_db_props(Target, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 1},
+ {changes, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [Rev1]}
+ ],
+
+ {ok, [{ok, PRevs}]} = cet_util:purge(Source, PurgeInfos),
+ ?assertEqual([Rev1], PRevs),
+
+ cet_util:assert_db_props(Source, [
+ {doc_count, 0},
+ {del_doc_count, 0},
+ {update_seq, 2},
+ {changes, 0},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]),
+
+ % Show that a purge on the source is
+ % not replicated to the target
+ {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
+ {ok, Doc2} = cet_util:open_doc(Target, foo),
+ [Rev2] = Doc2#doc_info.revs,
+ ?assertEqual(Rev1, Rev2#rev_info.rev),
+ ?assertEqual(Doc1, Doc2),
+
+ cet_util:assert_db_props(Target, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 1},
+ {changes, 1},
+ {purge_seq, 0},
+ {purge_infos, []}
+ ]),
+
+ % Show that replicating from the target
+ % back to the source reintroduces the doc
+ RepObject2 = {[
+ {<<"source">>, Target},
+ {<<"target">>, Source}
+ ]},
+
+ {ok, _} = couch_replicator:replicate(RepObject2, ?ADMIN_USER),
+ {ok, Doc3} = cet_util:open_doc(Source, foo),
+ [Revs3] = Doc3#doc_info.revs,
+ ?assertEqual(Rev1, Revs3#rev_info.rev),
+
+ cet_util:assert_db_props(Source, [
+ {doc_count, 1},
+ {del_doc_count, 0},
+ {update_seq, 3},
+ {changes, 1},
+ {purge_seq, 1},
+ {purge_infos, PurgeInfos}
+ ]).
+
+
+cet_purge_internal_repl_disabled({Source, Target}) ->
cet_util:with_config([{"mem3", "replicate_purges", "false"}], fun() ->
- repl(SrcDb, TgtDb),
+ repl(Source, Target),
- Actions1 = [
- {create, {<<"foo1">>, {[{<<"vsn">>, 1}]}}},
- {create, {<<"foo2">>, {[{<<"vsn">>, 2}]}}}
- ],
- ok = cet_util:apply_actions(SrcDb, Actions1),
- repl(SrcDb, TgtDb),
+ {ok, [Rev1, Rev2]} = cet_util:save_docs(Source, [
+ {[{'_id', foo1}, {vsn, 1}]},
+ {[{'_id', foo2}, {vsn, 2}]}
+ ]),
- Actions2 = [
- {purge, {<<"foo1">>, prev_rev(SrcDb, <<"foo1">>)}}
+ repl(Source, Target),
+
+ PurgeInfos1 = [
+ {cet_util:uuid(), <<"foo1">>, [Rev1]}
],
- ok = cet_util:apply_actions(SrcDb, Actions2),
+ {ok, [{ok, PRevs1}]} = cet_util:purge(Source, PurgeInfos1),
+ ?assertEqual([Rev1], PRevs1),
- Actions3 = [
- {purge, {<<"foo2">>, prev_rev(TgtDb, <<"foo2">>)}}
+ PurgeInfos2 = [
+ {cet_util:uuid(), <<"foo2">>, [Rev2]}
],
- ok = cet_util:apply_actions(TgtDb, Actions3),
+ {ok, [{ok, PRevs2}]} = cet_util:purge(Target, PurgeInfos2),
+ ?assertEqual([Rev2], PRevs2),
- SrcShard = make_shard(SrcDb),
- TgtShard = make_shard(TgtDb),
+ SrcShard = make_shard(Source),
+ TgtShard = make_shard(Target),
?assertEqual({ok, 0}, mem3_rep:go(SrcShard, TgtShard)),
+ ?assertEqual({ok, 0}, mem3_rep:go(TgtShard, SrcShard)),
- ?assertMatch([#full_doc_info{}], open_docs(SrcDb, [<<"foo2">>])),
- ?assertMatch([#full_doc_info{}], open_docs(TgtDb, [<<"foo1">>]))
+ ?assertMatch({ok, #doc_info{}}, cet_util:open_doc(Source, <<"foo2">>)),
+ ?assertMatch({ok, #doc_info{}}, cet_util:open_doc(Target, <<"foo1">>))
end).
-cet_purge_repl_simple_pull({SrcDb, TgtDb}) ->
- repl(SrcDb, TgtDb),
+cet_purge_repl_simple_pull({Source, Target}) ->
+ repl(Source, Target),
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}}
- ],
- ok = cet_util:apply_actions(SrcDb, Actions1),
- repl(SrcDb, TgtDb),
+ {ok, Rev} = cet_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
+ repl(Source, Target),
- Actions2 = [
- {purge, {<<"foo">>, prev_rev(TgtDb, <<"foo">>)}}
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [Rev]}
],
- ok = cet_util:apply_actions(TgtDb, Actions2),
- repl(SrcDb, TgtDb).
+ {ok, [{ok, PRevs}]} = cet_util:purge(Target, PurgeInfos),
+ ?assertEqual([Rev], PRevs),
+ repl(Source, Target).
-cet_purge_repl_simple_push({SrcDb, TgtDb}) ->
- repl(SrcDb, TgtDb),
+cet_purge_repl_simple_push({Source, Target}) ->
+ repl(Source, Target),
- Actions1 = [
- {create, {<<"foo">>, {[{<<"vsn">>, 1}]}}}
- ],
- ok = cet_util:apply_actions(SrcDb, Actions1),
- repl(SrcDb, TgtDb),
+ {ok, Rev} = cet_util:save_doc(Source, {[{'_id', foo}, {vsn, 1}]}),
+ repl(Source, Target),
- Actions2 = [
- {purge, {<<"foo">>, prev_rev(SrcDb, <<"foo">>)}}
+ PurgeInfos = [
+ {cet_util:uuid(), <<"foo">>, [Rev]}
],
- ok = cet_util:apply_actions(SrcDb, Actions2),
- repl(SrcDb, TgtDb).
+ {ok, [{ok, PRevs}]} = cet_util:purge(Source, PurgeInfos),
+ ?assertEqual([Rev], PRevs),
+ repl(Source, Target).
-repl(SrcDb, TgtDb) ->
- SrcShard = make_shard(SrcDb),
- TgtShard = make_shard(TgtDb),
+repl(Source, Target) ->
+ SrcShard = make_shard(Source),
+ TgtShard = make_shard(Target),
?assertEqual({ok, 0}, mem3_rep:go(SrcShard, TgtShard)),
- SrcTerm = cet_util:db_as_term(SrcDb, replication),
- TgtTerm = cet_util:db_as_term(TgtDb, replication),
+ SrcTerm = cet_util:db_as_term(Source, replication),
+ TgtTerm = cet_util:db_as_term(Target, replication),
Diff = cet_util:term_diff(SrcTerm, TgtTerm),
?assertEqual(nodiff, Diff).
@@ -116,18 +199,3 @@ make_shard(DbName) ->
dbname = DbName,
range = [0, 16#FFFFFFFF]
}.
-
-
-open_docs(DbName, DocIds) ->
- {ok, Db} = couch_db:open_int(DbName, [?ADMIN_CTX]),
- try
- couch_db_engine:open_docs(Db, DocIds)
- after
- couch_db:close(Db)
- end.
-
-
-prev_rev(DbName, DocId) ->
- [FDI] = open_docs(DbName, [DocId]),
- PrevRev = cet_util:prev_rev(FDI),
- PrevRev#rev_info.rev.
diff --git a/src/couch_db_engine_tests/src/cet_util.erl b/src/couch_db_engine_tests/src/cet_util.erl
index c9c8275b1..963202390 100644
--- a/src/couch_db_engine_tests/src/cet_util.erl
+++ b/src/couch_db_engine_tests/src/cet_util.erl
@@ -15,21 +15,23 @@
-compile(nowarn_export_all).
+-include_lib("eunit/include/eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(TEST_MODULES, [
- cet_test_open_close_delete,
- cet_test_get_set_props,
- cet_test_read_write_docs,
- cet_test_attachments,
- cet_test_purge_docs,
- cet_test_fold_docs,
- cet_test_fold_changes,
- cet_test_fold_purge_infos,
- cet_test_compaction,
- cet_test_purge_replication,
- cet_test_ref_counting
+ %% cet_test_open_close_delete,
+ %% cet_test_get_set_props,
+ %% cet_test_read_write_docs,
+ %% cet_test_attachments,
+ %% cet_test_purge_docs,
+ %% cet_test_fold_docs,
+ %% cet_test_fold_changes,
+ %% cet_test_fold_purge_infos,
+ %% cet_test_compaction,
+ %% cet_test_purge_replication,
+ cet_test_purge_bad_checkpoints %,
+ %% cet_test_ref_counting
]).
@@ -119,6 +121,117 @@ shutdown_db(Db) ->
end).
+save_doc(DbName, Json) ->
+ {ok, [Rev]} = save_docs(DbName, [Json], []),
+ {ok, Rev}.
+
+
+save_docs(DbName, JsonDocs) ->
+ save_docs(DbName, JsonDocs, []).
+
+
+save_docs(DbName, JsonDocs, Options) ->
+ Docs = lists:map(fun(JDoc) ->
+ couch_doc:from_json_obj(?JSON_DECODE(?JSON_ENCODE(JDoc)))
+ end, JsonDocs),
+ Opts = [full_commit | Options],
+ {ok, Db} = couch_db:open_int(DbName, []),
+ try
+ case lists:member(replicated_changes, Options) of
+ true ->
+ {ok, []} = couch_db:update_docs(
+ Db, Docs, Opts, replicated_changes),
+ {ok, lists:map(fun(Doc) ->
+ {Pos, [RevId | _]} = Doc#doc.revs,
+ {Pos, RevId}
+ end, Docs)};
+ false ->
+ {ok, Resp} = couch_db:update_docs(Db, Docs, Opts),
+ {ok, [Rev || {ok, Rev} <- Resp]}
+ end
+ after
+ couch_db:close(Db)
+ end.
+
+
+open_doc(DbName, DocId0) ->
+ DocId = ?JSON_DECODE(?JSON_ENCODE(DocId0)),
+ {ok, Db} = couch_db:open_int(DbName, []),
+ try
+ couch_db:get_doc_info(Db, DocId)
+ after
+ couch_db:close(Db)
+ end.
+
+
+purge(DbName, PurgeInfos) ->
+ purge(DbName, PurgeInfos, []).
+
+
+purge(DbName, PurgeInfos0, Options) when is_list(PurgeInfos0) ->
+ PurgeInfos = lists:map(fun({UUID, DocIdJson, Revs}) ->
+ {UUID, ?JSON_DECODE(?JSON_ENCODE(DocIdJson)), Revs}
+ end, PurgeInfos0),
+ {ok, Db} = couch_db:open_int(DbName, []),
+ try
+ couch_db:purge_docs(Db, PurgeInfos, Options)
+ after
+ couch_db:close(Db)
+ end.
+
+
+uuid() ->
+ couch_uuids:random().
+
+
+assert_db_props(DbName, Props) when is_binary(DbName) ->
+ {ok, Db} = couch_db:open_int(DbName, []),
+ try
+ assert_db_props(Db, Props)
+ after
+ couch_db:close(Db)
+ end;
+
+assert_db_props(Db, Props) ->
+ assert_each_prop(Db, Props).
+
+
+assert_each_prop(_Db, []) ->
+ ok;
+assert_each_prop(Db, [{doc_count, Expect} | Rest]) ->
+ {ok, DocCount} = couch_db:get_doc_count(Db),
+ ?assertEqual(Expect, DocCount),
+ assert_each_prop(Db, Rest);
+assert_each_prop(Db, [{del_doc_count, Expect} | Rest]) ->
+ {ok, DelDocCount} = couch_db:get_del_doc_count(Db),
+ ?assertEqual(Expect, DelDocCount),
+ assert_each_prop(Db, Rest);
+assert_each_prop(Db, [{update_seq, Expect} | Rest]) ->
+ UpdateSeq = couch_db:get_update_seq(Db),
+ ?assertEqual(Expect, UpdateSeq),
+ assert_each_prop(Db, Rest);
+assert_each_prop(Db, [{changes, Expect} | Rest]) ->
+ {ok, NumChanges} = couch_db:fold_changes(Db, 0, fun aep_changes/2, 0, []),
+ ?assertEqual(Expect, NumChanges),
+ assert_each_prop(Db, Rest);
+assert_each_prop(Db, [{purge_seq, Expect} | Rest]) ->
+ {ok, PurgeSeq} = couch_db:get_purge_seq(Db),
+ ?assertEqual(Expect, PurgeSeq),
+ assert_each_prop(Db, Rest);
+assert_each_prop(Db, [{purge_infos, Expect} | Rest]) ->
+ {ok, PurgeInfos} = couch_db:fold_purge_infos(Db, 0, fun aep_fold/2, [], []),
+ ?assertEqual(Expect, lists:reverse(PurgeInfos)),
+ assert_each_prop(Db, Rest).
+
+
+aep_changes(_A, Acc) ->
+ {ok, Acc + 1}.
+
+
+aep_fold({_PSeq, UUID, Id, Revs}, Acc) ->
+ {ok, [{UUID, Id, Revs} | Acc]}.
+
+
apply_actions(DbName, Actions) when is_binary(DbName) ->
{ok, Db0} = couch_db:open_int(DbName, [?ADMIN_CTX]),
{ok, Db1} = apply_actions(Db0, Actions),
@@ -312,12 +425,9 @@ db_as_term(Db) ->
db_as_term(Db, compact).
db_as_term(DbName, Type) when is_binary(DbName) ->
- {ok, Db} = couch_db:open_int(DbName, [?ADMIN_CTX]),
- try
+ couch_util:with_db(DbName, fun(Db) ->
db_as_term(Db, Type)
- after
- couch_db:close(Db)
- end;
+ end);
db_as_term(Db, Type) ->
[