diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2018-05-04 13:10:52 -0500 |
---|---|---|
committer | Paul J. Davis <paul.joseph.davis@gmail.com> | 2018-05-04 13:10:52 -0500 |
commit | 8f54e785c3db9d019d1b7d782316b9923a2570e7 (patch) | |
tree | c84358ea606bcc250cb241ed226214b118c8e839 | |
parent | 80fe295dcb29e0b23688acb6c9e65acd65a95321 (diff) | |
download | couchdb-8f54e785c3db9d019d1b7d782316b9923a2570e7.tar.gz |
squerge - new PSE test suite
-rw-r--r-- | src/couch/test/couch_db_purge_docs_tests.erl | 746 | ||||
-rw-r--r-- | src/couch_db_engine_tests/src/cet_test_purge_bad_checkpoints.erl | 121 | ||||
-rw-r--r-- | src/couch_db_engine_tests/src/cet_test_purge_docs.erl | 575 | ||||
-rw-r--r-- | src/couch_db_engine_tests/src/cet_test_purge_replication.erl | 190 | ||||
-rw-r--r-- | src/couch_db_engine_tests/src/cet_util.erl | 142 |
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) -> [ |