You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2018/03/16 20:00:05 UTC
[couchdb] 11/20: WIP - add test suite
This is an automated email from the ASF dual-hosted git repository.
davisp pushed a commit to branch COUCHDB-3326-clustered-purge-davisp-refactor
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 425a60ddbe98f9880d3e62b591a792848d144830
Author: Paul J. Davis <pa...@gmail.com>
AuthorDate: Wed Mar 14 14:09:16 2018 -0500
WIP - add test suite
---
src/couch/src/test_engine_compaction.erl | 116 +++++++-
src/couch/src/test_engine_fold_purged_docs.erl | 133 +++++++++
src/couch/src/test_engine_get_set_props.erl | 3 +-
src/couch/src/test_engine_purge_docs.erl | 35 ++-
src/couch/src/test_engine_util.erl | 162 +++++++----
src/couch/test/couch_db_purge_docs_tests.erl | 360 +++++++++++++++++++++++++
6 files changed, 744 insertions(+), 65 deletions(-)
diff --git a/src/couch/src/test_engine_compaction.erl b/src/couch/src/test_engine_compaction.erl
index 09a1e4e..51970ee 100644
--- a/src/couch/src/test_engine_compaction.erl
+++ b/src/couch/src/test_engine_compaction.erl
@@ -84,10 +84,8 @@ cet_compact_with_everything() ->
BarRev = test_engine_util:prev_rev(BarFDI),
Actions3 = [
- {batch, [
- {purge, {<<"foo">>, FooRev#rev_info.rev}},
- {purge, {<<"bar">>, BarRev#rev_info.rev}}
- ]}
+ {purge, {<<"foo">>, FooRev#rev_info.rev}},
+ {purge, {<<"bar">>, BarRev#rev_info.rev}}
],
{ok, St6} = test_engine_util:apply_actions(Engine, St5, Actions3),
@@ -97,7 +95,8 @@ cet_compact_with_everything() ->
{<<"foo">>, [FooRev#rev_info.rev]}
],
- ?assertEqual(PurgedIdRevs, lists:sort(Engine:get_last_purged(St6))),
+ {ok, PIdRevs6} = Engine:fold_purged_docs(St6, 0, fun fold_fun/2, [], []),
+ ?assertEqual(PurgedIdRevs, PIdRevs6),
{ok, St7} = try
[Att0, Att1, Att2, Att3, Att4] = test_engine_util:prep_atts(Engine, St6, [
@@ -131,6 +130,9 @@ cet_compact_with_everything() ->
end),
{ok, St10, undefined} = Engine:finish_compaction(St9, DbName, [], Term),
+ {ok, PIdRevs11} = Engine:fold_purged_docs(St10, 0, fun fold_fun/2, [], []),
+ ?assertEqual(PurgedIdRevs, PIdRevs11),
+
Db2 = test_engine_util:db_as_term(Engine, St10),
Diff = test_engine_util:term_diff(Db1, Db2),
?assertEqual(nodiff, Diff).
@@ -175,6 +177,106 @@ cet_recompact_updates() ->
?assertEqual(nodiff, Diff).
+cet_recompact_purge() ->
+ {ok, Engine, Path, St1} = test_engine_util:init_engine(dbpath),
+
+ Actions1 = [
+ {create, {<<"foo">>, []}},
+ {create, {<<"bar">>, []}},
+ {conflict, {<<"bar">>, [{<<"vsn">>, 2}]}},
+ {create, {<<"baz">>, []}}
+ ],
+
+ {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+ {ok, St3, DbName, _, Term} = test_engine_util:compact(Engine, St2, Path),
+
+ [BarFDI, BazFDI] = Engine:open_docs(St3, [<<"bar">>, <<"baz">>]),
+ BarRev = test_engine_util:prev_rev(BarFDI),
+ BazRev = test_engine_util:prev_rev(BazFDI),
+ Actions2 = [
+ {purge, {<<"bar">>, BarRev#rev_info.rev}},
+ {purge, {<<"baz">>, BazRev#rev_info.rev}}
+ ],
+ {ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions2),
+ Db1 = test_engine_util:db_as_term(Engine, St4),
+
+ {ok, St5, NewPid} = Engine:finish_compaction(St4, DbName, [], Term),
+
+ ?assertEqual(true, is_pid(NewPid)),
+ Ref = erlang:monitor(process, NewPid),
+
+ NewTerm = receive
+ {'$gen_cast', {compact_done, Engine, Term0}} ->
+ Term0;
+ {'DOWN', Ref, _, _, Reason} ->
+ erlang:error({compactor_died, Reason});
+ {'$gen_call', {NewPid, Ref2}, get_disposable_purge_seq} ->
+ NewPid!{Ref2, {ok, 0}},
+ receive
+ {'$gen_cast', {compact_done, Engine, Term0}} ->
+ Term0;
+ {'DOWN', Ref, _, _, Reason} ->
+ erlang:error({compactor_died, Reason})
+ after 10000 ->
+ erlang:error(compactor_timed_out)
+ end
+ after 10000 ->
+ erlang:error(compactor_timed_out)
+ end,
+
+ {ok, St6, undefined} = Engine:finish_compaction(St5, DbName, [], NewTerm),
+ Db2 = test_engine_util:db_as_term(Engine, St6),
+ Diff = test_engine_util:term_diff(Db1, Db2),
+ ?assertEqual(nodiff, Diff).
+
+
+% temporary ignoring this test as it times out
+ignore_cet_compact_purged_docs_limit() ->
+ {ok, Engine, Path, St1} = test_engine_util:init_engine(dbpath),
+ % create NumDocs docs
+ NumDocs = 1200,
+ {RActions, RIds} = lists:foldl(fun(Id, {CActions, CIds}) ->
+ Id1 = docid(Id),
+ Action = {create, {Id1, [{<<"int">>, Id}]}},
+ {[Action| CActions], [Id1| CIds]}
+ end, {[], []}, lists:seq(1, NumDocs)),
+ Ids = lists:reverse(RIds),
+ {ok, St2} = test_engine_util:apply_actions(Engine, St1,
+ lists:reverse(RActions)),
+
+ % purge NumDocs docs
+ FDIs = Engine:open_docs(St2, Ids),
+ RevActions2 = lists:foldl(fun(FDI, CActions) ->
+ Id = FDI#full_doc_info.id,
+ PrevRev = test_engine_util:prev_rev(FDI),
+ Rev = PrevRev#rev_info.rev,
+ [{purge, {Id, Rev}}| CActions]
+ end, [], FDIs),
+ {ok, St3} = test_engine_util:apply_actions(Engine, St2,
+ lists:reverse(RevActions2)),
+
+ % check that before compaction all NumDocs of purge_requests
+ % are in purge_tree,
+ % even if NumDocs=1200 is greater than purged_docs_limit=1000
+ {ok, PurgedIdRevs} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
+ ?assertEqual(1, Engine:get_oldest_purge_seq(St3)),
+ ?assertEqual(NumDocs, length(PurgedIdRevs)),
+
+ % compact db
+ {ok, St4, DbName, _, Term} = test_engine_util:compact(Engine, St3, Path),
+ {ok, St5, undefined} = Engine:finish_compaction(St4, DbName, [], Term),
+
+ % check that after compaction only purged_docs_limit purge_requests
+ % are in purge_tree
+ PurgedDocsLimit = Engine:get_purged_docs_limit(St5),
+ OldestPSeq = Engine:get_oldest_purge_seq(St5),
+ {ok, PurgedIdRevs2} = Engine:fold_purged_docs(
+ St5, OldestPSeq - 1, fun fold_fun/2, [], []),
+ ExpectedOldestPSeq = NumDocs - PurgedDocsLimit + 1,
+ ?assertEqual(ExpectedOldestPSeq, OldestPSeq),
+ ?assertEqual(PurgedDocsLimit, length(PurgedIdRevs2)).
+
+
docid(I) ->
Str = io_lib:format("~4..0b", [I]),
iolist_to_binary(Str).
@@ -183,3 +285,7 @@ docid(I) ->
local_docid(I) ->
Str = io_lib:format("_local/~4..0b", [I]),
iolist_to_binary(Str).
+
+
+fold_fun({_PSeq, _UUID, Id, Revs}, Acc) ->
+ [{Id, Revs} | Acc].
diff --git a/src/couch/src/test_engine_fold_purged_docs.erl b/src/couch/src/test_engine_fold_purged_docs.erl
new file mode 100644
index 0000000..1dc0885
--- /dev/null
+++ b/src/couch/src/test_engine_fold_purged_docs.erl
@@ -0,0 +1,133 @@
+% 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(test_engine_fold_purged_docs).
+-compile(export_all).
+
+
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+
+-define(NUM_DOCS, 100).
+
+
+cet_empty_purged_docs() ->
+ {ok, Engine, St} = test_engine_util:init_engine(),
+ ?assertEqual({ok, []}, Engine:fold_purged_docs(St, 0, fun fold_fun/2, [], [])).
+
+
+cet_all_purged_docs() ->
+ {ok, Engine, St1} = test_engine_util:init_engine(),
+
+ {RActions, RIds} = lists:foldl(fun(Id, {CActions, CIds}) ->
+ Id1 = docid(Id),
+ Action = {create, {Id1, [{<<"int">>, Id}]}},
+ {[Action| CActions], [Id1| CIds]}
+ end, {[], []}, lists:seq(1, ?NUM_DOCS)),
+ Actions = lists:reverse(RActions),
+ Ids = lists:reverse(RIds),
+ {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions),
+
+ FDIs = Engine:open_docs(St2, Ids),
+ {RevActions2, RevIdRevs} = lists:foldl(fun(FDI, {CActions, CIdRevs}) ->
+ Id = FDI#full_doc_info.id,
+ PrevRev = test_engine_util:prev_rev(FDI),
+ Rev = PrevRev#rev_info.rev,
+ Action = {purge, {Id, Rev}},
+ {[Action| CActions], [{Id, [Rev]}| CIdRevs]}
+ end, {[], []}, FDIs),
+ {Actions2, IdsRevs} = {lists:reverse(RevActions2), lists:reverse(RevIdRevs)},
+
+ {ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2),
+ {ok, PurgedIdRevs} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
+ ?assertEqual(IdsRevs, lists:reverse(PurgedIdRevs)).
+
+
+cet_start_seq() ->
+ {ok, Engine, St1} = test_engine_util:init_engine(),
+ Actions1 = [
+ {create, {docid(1), [{<<"int">>, 1}]}},
+ {create, {docid(2), [{<<"int">>, 2}]}},
+ {create, {docid(3), [{<<"int">>, 3}]}},
+ {create, {docid(4), [{<<"int">>, 4}]}},
+ {create, {docid(5), [{<<"int">>, 5}]}}
+ ],
+ Ids = [docid(1), docid(2), docid(3), docid(4), docid(5)],
+ {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+
+ FDIs = Engine:open_docs(St2, Ids),
+ {RActions2, RIdRevs} = lists:foldl(fun(FDI, {CActions, CIdRevs}) ->
+ Id = FDI#full_doc_info.id,
+ PrevRev = test_engine_util:prev_rev(FDI),
+ Rev = PrevRev#rev_info.rev,
+ Action = {purge, {Id, Rev}},
+ {[Action| CActions], [{Id, [Rev]}| CIdRevs]}
+ end, {[], []}, FDIs),
+ {ok, St3} = test_engine_util:apply_actions(Engine, St2, lists:reverse(RActions2)),
+
+ StartSeq = 3,
+ StartSeqIdRevs = lists:nthtail(StartSeq, lists:reverse(RIdRevs)),
+ {ok, PurgedIdRevs} = Engine:fold_purged_docs(St3, StartSeq, fun fold_fun/2, [], []),
+ ?assertEqual(StartSeqIdRevs, lists:reverse(PurgedIdRevs)).
+
+
+cet_id_rev_repeated() ->
+ {ok, Engine, St1} = test_engine_util:init_engine(),
+
+ Actions1 = [
+ {create, {<<"foo">>, [{<<"vsn">>, 1}]}},
+ {conflict, {<<"foo">>, [{<<"vsn">>, 2}]}}
+ ],
+ {ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+
+ [FDI1] = Engine:open_docs(St2, [<<"foo">>]),
+ PrevRev1 = test_engine_util:prev_rev(FDI1),
+ Rev1 = PrevRev1#rev_info.rev,
+ Actions2 = [
+ {purge, {<<"foo">>, Rev1}}
+ ],
+ {ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2),
+ PurgedIdRevs0 = [{<<"foo">>, [Rev1]}],
+ {ok, PurgedIdRevs1} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
+ ?assertEqual(PurgedIdRevs0, PurgedIdRevs1),
+ ?assertEqual(1, Engine:get_purge_seq(St3)),
+
+ % purge the same Id,Rev when the doc still exists
+ {ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions2),
+ {ok, PurgedIdRevs2} = Engine:fold_purged_docs(St4, 0, fun fold_fun/2, [], []),
+ ?assertEqual(PurgedIdRevs0, PurgedIdRevs2),
+ ?assertEqual(1, Engine:get_purge_seq(St4)),
+
+ [FDI2] = Engine:open_docs(St4, [<<"foo">>]),
+ PrevRev2 = test_engine_util:prev_rev(FDI2),
+ Rev2 = PrevRev2#rev_info.rev,
+ Actions3 = [
+ {purge, {<<"foo">>, Rev2}}
+ ],
+ {ok, St5} = test_engine_util:apply_actions(Engine, St4, Actions3),
+ PurgedIdRevs00 = [{<<"foo">>, [Rev1]}, {<<"foo">>, [Rev2]}],
+
+ % purge the same Id,Rev when the doc was completely purged
+ {ok, St6} = test_engine_util:apply_actions(Engine, St5, Actions3),
+ {ok, PurgedIdRevs3} = Engine:fold_purged_docs(St6, 0, fun fold_fun/2, [], []),
+ ?assertEqual(PurgedIdRevs00, lists:reverse(PurgedIdRevs3)),
+ ?assertEqual(2, Engine:get_purge_seq(St6)).
+
+
+fold_fun({_PSeq, _UUID, Id, Revs}, Acc) ->
+ [{Id, Revs} | Acc].
+
+
+docid(I) ->
+ Str = io_lib:format("~4..0b", [I]),
+ iolist_to_binary(Str).
diff --git a/src/couch/src/test_engine_get_set_props.erl b/src/couch/src/test_engine_get_set_props.erl
index 6d2a447..ac6aca8 100644
--- a/src/couch/src/test_engine_get_set_props.erl
+++ b/src/couch/src/test_engine_get_set_props.erl
@@ -34,7 +34,8 @@ cet_default_props() ->
?assertEqual(true, is_integer(Engine:get_disk_version(St))),
?assertEqual(0, Engine:get_update_seq(St)),
?assertEqual(0, Engine:get_purge_seq(St)),
- ?assertEqual([], Engine:get_last_purged(St)),
+ ?assertEqual(true, is_integer(Engine:get_purged_docs_limit(St))),
+ ?assertEqual(true, Engine:get_purged_docs_limit(St) > 0),
?assertEqual(dso, Engine:get_security(St)),
?assertEqual(1000, Engine:get_revs_limit(St)),
?assertMatch(<<_:32/binary>>, Engine:get_uuid(St)),
diff --git a/src/couch/src/test_engine_purge_docs.erl b/src/couch/src/test_engine_purge_docs.erl
index e5bf249..a1dbae7 100644
--- a/src/couch/src/test_engine_purge_docs.erl
+++ b/src/couch/src/test_engine_purge_docs.erl
@@ -25,12 +25,13 @@ cet_purge_simple() ->
{create, {<<"foo">>, [{<<"vsn">>, 1}]}}
],
{ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+ {ok, PIdRevs2} = Engine:fold_purged_docs(St2, 0, fun fold_fun/2, [], []),
?assertEqual(1, Engine:get_doc_count(St2)),
?assertEqual(0, Engine:get_del_doc_count(St2)),
?assertEqual(1, Engine:get_update_seq(St2)),
?assertEqual(0, Engine:get_purge_seq(St2)),
- ?assertEqual([], Engine:get_last_purged(St2)),
+ ?assertEqual([], PIdRevs2),
[FDI] = Engine:open_docs(St2, [<<"foo">>]),
PrevRev = test_engine_util:prev_rev(FDI),
@@ -40,12 +41,13 @@ cet_purge_simple() ->
{purge, {<<"foo">>, Rev}}
],
{ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2),
+ {ok, PIdRevs3} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
?assertEqual(0, Engine:get_doc_count(St3)),
?assertEqual(0, Engine:get_del_doc_count(St3)),
?assertEqual(2, Engine:get_update_seq(St3)),
?assertEqual(1, Engine:get_purge_seq(St3)),
- ?assertEqual([{<<"foo">>, [Rev]}], Engine:get_last_purged(St3)).
+ ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3).
cet_purge_conflicts() ->
@@ -56,12 +58,13 @@ cet_purge_conflicts() ->
{conflict, {<<"foo">>, [{<<"vsn">>, 2}]}}
],
{ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+ {ok, PIdRevs2} = Engine:fold_purged_docs(St2, 0, fun fold_fun/2, [], []),
?assertEqual(1, Engine:get_doc_count(St2)),
?assertEqual(0, Engine:get_del_doc_count(St2)),
?assertEqual(2, Engine:get_update_seq(St2)),
?assertEqual(0, Engine:get_purge_seq(St2)),
- ?assertEqual([], Engine:get_last_purged(St2)),
+ ?assertEqual([], PIdRevs2),
[FDI1] = Engine:open_docs(St2, [<<"foo">>]),
PrevRev1 = test_engine_util:prev_rev(FDI1),
@@ -71,12 +74,13 @@ cet_purge_conflicts() ->
{purge, {<<"foo">>, Rev1}}
],
{ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2),
+ {ok, PIdRevs3} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
?assertEqual(1, Engine:get_doc_count(St3)),
?assertEqual(0, Engine:get_del_doc_count(St3)),
- ?assertEqual(4, Engine:get_update_seq(St3)),
+ ?assertEqual(3, Engine:get_update_seq(St3)),
?assertEqual(1, Engine:get_purge_seq(St3)),
- ?assertEqual([{<<"foo">>, [Rev1]}], Engine:get_last_purged(St3)),
+ ?assertEqual([{<<"foo">>, [Rev1]}], PIdRevs3),
[FDI2] = Engine:open_docs(St3, [<<"foo">>]),
PrevRev2 = test_engine_util:prev_rev(FDI2),
@@ -86,12 +90,13 @@ cet_purge_conflicts() ->
{purge, {<<"foo">>, Rev2}}
],
{ok, St4} = test_engine_util:apply_actions(Engine, St3, Actions3),
+ {ok, PIdRevs4} = Engine:fold_purged_docs(St4, 0, fun fold_fun/2, [], []),
?assertEqual(0, Engine:get_doc_count(St4)),
?assertEqual(0, Engine:get_del_doc_count(St4)),
- ?assertEqual(5, Engine:get_update_seq(St4)),
+ ?assertEqual(4, Engine:get_update_seq(St4)),
?assertEqual(2, Engine:get_purge_seq(St4)),
- ?assertEqual([{<<"foo">>, [Rev2]}], Engine:get_last_purged(St4)).
+ ?assertEqual([{<<"foo">>, [Rev2]}, {<<"foo">>, [Rev1]}], PIdRevs4).
cet_add_delete_purge() ->
@@ -103,12 +108,13 @@ cet_add_delete_purge() ->
],
{ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+ {ok, PIdRevs2} = Engine:fold_purged_docs(St2, 0, fun fold_fun/2, [], []),
?assertEqual(0, Engine:get_doc_count(St2)),
?assertEqual(1, Engine:get_del_doc_count(St2)),
?assertEqual(2, Engine:get_update_seq(St2)),
?assertEqual(0, Engine:get_purge_seq(St2)),
- ?assertEqual([], Engine:get_last_purged(St2)),
+ ?assertEqual([], PIdRevs2),
[FDI] = Engine:open_docs(St2, [<<"foo">>]),
PrevRev = test_engine_util:prev_rev(FDI),
@@ -118,12 +124,13 @@ cet_add_delete_purge() ->
{purge, {<<"foo">>, Rev}}
],
{ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2),
+ {ok, PIdRevs3} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
?assertEqual(0, Engine:get_doc_count(St3)),
?assertEqual(0, Engine:get_del_doc_count(St3)),
?assertEqual(3, Engine:get_update_seq(St3)),
?assertEqual(1, Engine:get_purge_seq(St3)),
- ?assertEqual([{<<"foo">>, [Rev]}], Engine:get_last_purged(St3)).
+ ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3).
cet_add_two_purge_one() ->
@@ -135,12 +142,13 @@ cet_add_two_purge_one() ->
],
{ok, St2} = test_engine_util:apply_actions(Engine, St1, Actions1),
+ {ok, PIdRevs2} = Engine:fold_purged_docs(St2, 0, fun fold_fun/2, [], []),
?assertEqual(2, Engine:get_doc_count(St2)),
?assertEqual(0, Engine:get_del_doc_count(St2)),
?assertEqual(2, Engine:get_update_seq(St2)),
?assertEqual(0, Engine:get_purge_seq(St2)),
- ?assertEqual([], Engine:get_last_purged(St2)),
+ ?assertEqual([], PIdRevs2),
[FDI] = Engine:open_docs(St2, [<<"foo">>]),
PrevRev = test_engine_util:prev_rev(FDI),
@@ -150,9 +158,14 @@ cet_add_two_purge_one() ->
{purge, {<<"foo">>, Rev}}
],
{ok, St3} = test_engine_util:apply_actions(Engine, St2, Actions2),
+ {ok, PIdRevs3} = Engine:fold_purged_docs(St3, 0, fun fold_fun/2, [], []),
?assertEqual(1, Engine:get_doc_count(St3)),
?assertEqual(0, Engine:get_del_doc_count(St3)),
?assertEqual(3, Engine:get_update_seq(St3)),
?assertEqual(1, Engine:get_purge_seq(St3)),
- ?assertEqual([{<<"foo">>, [Rev]}], Engine:get_last_purged(St3)).
+ ?assertEqual([{<<"foo">>, [Rev]}], PIdRevs3).
+
+
+fold_fun({_Pseq, _UUID, Id, Revs}, Acc) ->
+ [{Id, Revs} | Acc].
\ No newline at end of file
diff --git a/src/couch/src/test_engine_util.erl b/src/couch/src/test_engine_util.erl
index 8999753..bd49969 100644
--- a/src/couch/src/test_engine_util.erl
+++ b/src/couch/src/test_engine_util.erl
@@ -24,6 +24,7 @@
test_engine_attachments,
test_engine_fold_docs,
test_engine_fold_changes,
+ test_engine_fold_purged_docs,
test_engine_purge_docs,
test_engine_compaction,
test_engine_ref_counting
@@ -132,28 +133,35 @@ apply_action(Engine, St, Action) ->
apply_batch(Engine, St, [Action]).
+apply_batch(Engine, St, [{purge, {Id, Revs}}]) ->
+ UpdateSeq = Engine:get_update_seq(St) + 1,
+ case gen_write(Engine, St, {purge, {Id, Revs}}, UpdateSeq) of
+ {_, _, purged_before}->
+ St;
+ {Pair, _, {Id, PRevs}} ->
+ UUID = couch_uuids:new(),
+ {ok, NewSt} = Engine:purge_doc_revs(
+ St, [Pair], [{UUID, Id, PRevs}]),
+ NewSt
+ end;
+
apply_batch(Engine, St, Actions) ->
UpdateSeq = Engine:get_update_seq(St) + 1,
- AccIn = {UpdateSeq, [], [], []},
+ AccIn = {UpdateSeq, [], []},
AccOut = lists:foldl(fun(Action, Acc) ->
- {SeqAcc, DocAcc, LDocAcc, PurgeAcc} = Acc,
+ {SeqAcc, DocAcc, LDocAcc} = Acc,
case Action of
{_, {<<"_local/", _/binary>>, _}} ->
LDoc = gen_local_write(Engine, St, Action),
- {SeqAcc, DocAcc, [LDoc | LDocAcc], PurgeAcc};
+ {SeqAcc, DocAcc, [LDoc | LDocAcc]};
_ ->
- case gen_write(Engine, St, Action, SeqAcc) of
- {_OldFDI, _NewFDI} = Pair ->
- {SeqAcc + 1, [Pair | DocAcc], LDocAcc, PurgeAcc};
- {Pair, NewSeqAcc, NewPurgeInfo} ->
- NewPurgeAcc = [NewPurgeInfo | PurgeAcc],
- {NewSeqAcc, [Pair | DocAcc], LDocAcc, NewPurgeAcc}
- end
+ {OldFDI, NewFDI} = gen_write(Engine, St, Action, SeqAcc),
+ {SeqAcc + 1, [{OldFDI, NewFDI} | DocAcc], LDocAcc}
end
end, AccIn, Actions),
- {_, Docs0, LDocs, PurgeIdRevs} = AccOut,
+ {_, Docs0, LDocs} = AccOut,
Docs = lists:reverse(Docs0),
- {ok, NewSt} = Engine:write_doc_infos(St, Docs, LDocs, PurgeIdRevs),
+ {ok, NewSt} = Engine:write_doc_infos(St, Docs, LDocs),
NewSt.
@@ -221,39 +229,71 @@ gen_write(Engine, St, {create, {DocId, Body, Atts0}}, UpdateSeq) ->
}};
gen_write(Engine, St, {purge, {DocId, PrevRevs0, _}}, UpdateSeq) ->
- [#full_doc_info{} = PrevFDI] = Engine:open_docs(St, [DocId]),
- PrevRevs = if is_list(PrevRevs0) -> PrevRevs0; true -> [PrevRevs0] end,
-
- #full_doc_info{
- rev_tree = PrevTree
- } = PrevFDI,
-
- {NewTree, RemRevs} = couch_key_tree:remove_leafs(PrevTree, PrevRevs),
- RemovedAll = lists:sort(RemRevs) == lists:sort(PrevRevs),
- if RemovedAll -> ok; true ->
- % If we didn't purge all the requested revisions
- % then its a bug in the test.
- erlang:error({invalid_purge_test_revs, PrevRevs})
- end,
+ case Engine:open_docs(St, [DocId]) of
+ [not_found] ->
+ % Check if this doc has been purged before
+ FoldFun = fun({_PSeq, _UUID, Id, _Revs}, _Acc) ->
+ case Id of
+ DocId -> true;
+ _ -> false
+ end
+ end,
+ {ok, IsPurgedBefore} = Engine:fold_purged_docs(
+ St, 0, FoldFun, false, []),
+ case IsPurgedBefore of
+ true -> {{}, UpdateSeq, purged_before};
+ false -> erlang:error({invalid_purge_test_id, DocId})
+ end;
+ [#full_doc_info{} = PrevFDI] ->
+ PrevRevs = if is_list(PrevRevs0) -> PrevRevs0; true -> [PrevRevs0] end,
+
+ #full_doc_info{
+ rev_tree = PrevTree
+ } = PrevFDI,
+
+ {NewTree, RemRevs0} = couch_key_tree:remove_leafs(PrevTree, PrevRevs),
+ {RemRevs, NotRemRevs} = lists:partition(fun(R) ->
+ lists:member(R, RemRevs0) end, PrevRevs),
+
+ if NotRemRevs == [] -> ok; true ->
+ % Check if these Revs have been purged before
+ FoldFun = fun({_Pseq, _UUID, Id, Revs}, Acc) ->
+ case Id of
+ DocId -> Acc ++ Revs;
+ _ -> Acc
+ end
+ end,
+ {ok, PurgedRevs} = Engine:fold_purged_docs(St, 0, FoldFun, [], []),
+ case lists:subtract(PrevRevs, PurgedRevs) of [] -> ok; _ ->
+ % If we didn't purge all the requested revisions
+ % and they haven't been purged before
+ % then its a bug in the test.
+ erlang:error({invalid_purge_test_revs, PrevRevs})
+ end
+ end,
+
+ case {RemRevs, NewTree} of
+ {[], _} ->
+ {{PrevFDI, PrevFDI}, UpdateSeq, purged_before};
+ {_, []} ->
+ % We've completely purged the document
+ {{PrevFDI, not_found}, UpdateSeq, {DocId, RemRevs}};
+ _ ->
+ % We have to relabel the update_seq of all
+ % leaves. See couch_db_updater for details.
+ {NewNewTree, NewUpdateSeq} = couch_key_tree:mapfold(fun
+ (_RevId, Leaf, leaf, InnerSeqAcc) ->
+ {Leaf#leaf{seq = InnerSeqAcc}, InnerSeqAcc + 1};
+ (_RevId, Value, _Type, InnerSeqAcc) ->
+ {Value, InnerSeqAcc}
+ end, UpdateSeq, NewTree),
+ NewFDI = PrevFDI#full_doc_info{
+ update_seq = NewUpdateSeq - 1,
+ rev_tree = NewNewTree
+ },
+ {{PrevFDI, NewFDI}, NewUpdateSeq, {DocId, RemRevs}}
- case NewTree of
- [] ->
- % We've completely purged the document
- {{PrevFDI, not_found}, UpdateSeq, {DocId, RemRevs}};
- _ ->
- % We have to relabel the update_seq of all
- % leaves. See couch_db_updater for details.
- {NewNewTree, NewUpdateSeq} = couch_key_tree:mapfold(fun
- (_RevId, Leaf, leaf, InnerSeqAcc) ->
- {Leaf#leaf{seq = InnerSeqAcc}, InnerSeqAcc + 1};
- (_RevId, Value, _Type, InnerSeqAcc) ->
- {Value, InnerSeqAcc}
- end, UpdateSeq, NewTree),
- NewFDI = PrevFDI#full_doc_info{
- update_seq = NewUpdateSeq - 1,
- rev_tree = NewNewTree
- },
- {{PrevFDI, NewFDI}, NewUpdateSeq, {DocId, RemRevs}}
+ end
end;
gen_write(Engine, St, {Action, {DocId, Body, Atts0}}, UpdateSeq) ->
@@ -408,7 +448,8 @@ db_as_term(Engine, St) ->
{props, db_props_as_term(Engine, St)},
{docs, db_docs_as_term(Engine, St)},
{local_docs, db_local_docs_as_term(Engine, St)},
- {changes, db_changes_as_term(Engine, St)}
+ {changes, db_changes_as_term(Engine, St)},
+ {purged_docs, db_purged_docs_as_term(Engine, St)}
].
@@ -419,7 +460,7 @@ db_props_as_term(Engine, St) ->
get_disk_version,
get_update_seq,
get_purge_seq,
- get_last_purged,
+ get_purged_docs_limit,
get_security,
get_revs_limit,
get_uuid,
@@ -452,6 +493,15 @@ db_changes_as_term(Engine, St) ->
end, Changes)).
+db_purged_docs_as_term(Engine, St) ->
+ StartPSeq = Engine:get_oldest_purge_seq(St) - 1,
+ FoldFun = fun({PSeq, UUID, Id, Revs}, Acc) ->
+ [{PSeq, UUID, Id, Revs} | Acc]
+ end,
+ {ok, PDocs} = Engine:fold_purged_docs(St, StartPSeq, FoldFun, [], []),
+ lists:reverse(PDocs).
+
+
fdi_to_term(Engine, St, FDI) ->
#full_doc_info{
id = DocId,
@@ -577,8 +627,24 @@ compact(Engine, St1, DbPath) ->
Term = receive
{'$gen_cast', {compact_done, Engine, Term0}} ->
Term0;
- {'DOWN', Ref, _, _, Reason} ->
- erlang:error({compactor_died, Reason})
+ erlang:error({compactor_died, Reason});
+ {'$gen_call', {Pid, Ref2}, get_disposable_purge_seq} ->
+ % assuming no client exists (no internal replications or indexes)
+ PSeq = Engine:get_purge_seq(St2),
+ OldestPSeq = Engine:get_oldest_purge_seq(St2),
+ PDocsLimit = Engine:get_purged_docs_limit(St2),
+ ExpectedDispPSeq = PSeq - PDocsLimit,
+ DisposablePSeq = if ExpectedDispPSeq > 0 -> ExpectedDispPSeq;
+ true -> OldestPSeq - 1 end,
+ Pid!{Ref2, {ok, DisposablePSeq}},
+ receive
+ {'$gen_cast', {compact_done, Engine, Term0}} ->
+ Term0;
+ {'DOWN', Ref, _, _, Reason} ->
+ erlang:error({compactor_died, Reason})
+ after 10000 ->
+ erlang:error(compactor_timed_out)
+ end
after ?COMPACTOR_TIMEOUT ->
erlang:error(compactor_timed_out)
end,
diff --git a/src/couch/test/couch_db_purge_docs_tests.erl b/src/couch/test/couch_db_purge_docs_tests.erl
new file mode 100644
index 0000000..1608957
--- /dev/null
+++ b/src/couch/test/couch_db_purge_docs_tests.erl
@@ -0,0 +1,360 @@
+% 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").
+
+
+setup() ->
+ DbName = ?tempdb(),
+ {ok, _Db} = create_db(DbName),
+ DbName.
+
+teardown(DbName) ->
+ delete_db(DbName),
+ ok.
+
+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()]
+ },
+ purge_with_replication()
+ ]
+
+ }.
+
+
+couch_db_purge_docs() ->
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [
+ fun purge_simple/1,
+ fun add_delete_purge/1,
+ fun add_two_purge_one/1,
+ fun purge_id_not_exist/1,
+ fun purge_non_leaf_rev/1,
+ fun purge_conflicts/1,
+ fun purge_deep_tree/1
+ ]
+ }.
+
+
+purge_simple(DbName) ->
+ ?_test(
+ begin
+ {ok, Db} = couch_db:open_int(DbName, []),
+ Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1.1}]},
+ Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 1.2}]},
+ {ok, Rev} = save_doc(Db, Doc1),
+ {ok, Rev2} = save_doc(Db, Doc2),
+ couch_db:ensure_full_commit(Db),
+
+ {ok, Db2} = couch_db:reopen(Db),
+ ?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)),
+
+ UUID = couch_uuids:new(), UUID2 = couch_uuids:new(),
+ {ok, [{ok, PRevs}, {ok, PRevs2}]} = couch_db:purge_docs(
+ Db2, [{UUID, <<"foo1">>, [Rev]}, {UUID2, <<"foo2">>, [Rev2]}]
+ ),
+
+ ?assertEqual([Rev], PRevs),
+ ?assertEqual([Rev2], PRevs2),
+
+ {ok, Db3} = couch_db:reopen(Db2),
+ {ok, PIdsRevs} = couch_db:fold_purged_docs(
+ 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(2, couch_db_engine:get_purge_seq(Db3)),
+ ?assertEqual([{<<"foo2">>, [Rev2]}, {<<"foo1">>, [Rev]}], PIdsRevs)
+ end).
+
+
+add_delete_purge(DbName) ->
+ ?_test(
+ begin
+ {ok, Db0} = couch_db:open_int(DbName, []),
+ Doc0 = {[{<<"_id">>,<<"foo">>}, {<<"vsn">>, 1}]},
+ {ok, Rev} = save_doc(Db0, Doc0),
+ couch_db:ensure_full_commit(Db0),
+ {ok, Db1} = couch_db:reopen(Db0),
+
+ Doc1 = {[
+ {<<"_id">>, <<"foo">>}, {<<"vsn">>, 2},
+ {<<"_rev">>, couch_doc:rev_to_str(Rev)},
+ {<<"_deleted">>, true}]
+ },
+ {ok, Rev2} = save_doc(Db1, Doc1),
+ couch_db:ensure_full_commit(Db1),
+
+ {ok, Db2} = couch_db:reopen(Db1),
+ {ok, PIdsRevs1} = couch_db:fold_purged_docs(
+ Db2, 0, fun fold_fun/2, [], []),
+ ?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([], PIdsRevs1),
+
+ UUID = couch_uuids:new(),
+ {ok, [{ok, PRevs}]} = couch_db:purge_docs(
+ Db2, [{UUID, <<"foo">>, [Rev2]}]),
+ ?assertEqual([Rev2], PRevs),
+
+ {ok, Db3} = couch_db:reopen(Db2),
+ {ok, PIdsRevs2} = couch_db:fold_purged_docs(
+ 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">>, [Rev2]}], PIdsRevs2)
+ end).
+
+
+add_two_purge_one(DbName) ->
+ ?_test(
+ begin
+ {ok, Db} = couch_db:open_int(DbName, []),
+ Doc1 = {[{<<"_id">>, <<"foo1">>}, {<<"vsn">>, 1}]},
+ Doc2 = {[{<<"_id">>, <<"foo2">>}, {<<"vsn">>, 2}]},
+ {ok, Rev} = save_doc(Db, Doc1),
+ {ok, _Rev2} = save_doc(Db, Doc2),
+ couch_db:ensure_full_commit(Db),
+
+ {ok, Db2} = couch_db:reopen(Db),
+ ?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)),
+
+ UUID = couch_uuids:new(),
+ {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db2,
+ [{UUID, <<"foo1">>, [Rev]}]),
+ ?assertEqual([Rev], PRevs),
+
+ {ok, Db3} = couch_db:reopen(Db2),
+ {ok, PIdsRevs} = couch_db:fold_purged_docs(
+ 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([{<<"foo1">>, [Rev]}], PIdsRevs)
+ end).
+
+
+purge_id_not_exist(DbName) ->
+ ?_test(
+ begin
+ {ok, Db} = couch_db:open_int(DbName, []),
+ UUID = couch_uuids:new(),
+ {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db,
+ [{UUID, <<"foo">>, [{0, <<0>>}]}]),
+ ?assertEqual([], PRevs),
+
+ {ok, Db2} = couch_db:reopen(Db),
+ {ok, PIdsRevs} = couch_db:fold_purged_docs(
+ Db2, 0, fun fold_fun/2, [], []),
+ ?assertEqual(0, couch_db_engine:get_doc_count(Db2)),
+ ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)),
+ ?assertEqual(0, couch_db_engine:get_update_seq(Db2)),
+ ?assertEqual(0, couch_db_engine:get_purge_seq(Db2)),
+ ?assertEqual([], PIdsRevs)
+ end).
+
+
+purge_non_leaf_rev(DbName) ->
+ ?_test(
+ begin
+ {ok, Db} = couch_db:open_int(DbName, []),
+ Doc0 = {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 1}]},
+ {ok, Rev} = save_doc(Db, Doc0),
+ couch_db:ensure_full_commit(Db),
+ {ok, Db2} = couch_db:reopen(Db),
+
+ Doc1 = {[
+ {<<"_id">>, <<"foo">>}, {<<"vsn">>, 2},
+ {<<"_rev">>, couch_doc:rev_to_str(Rev)}
+ ]},
+ {ok, _Rev2} = save_doc(Db2, Doc1),
+ couch_db:ensure_full_commit(Db2),
+ {ok, Db3} = couch_db:reopen(Db2),
+
+ UUID = couch_uuids:new(),
+ {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db3,
+ [{UUID, <<"foo">>, [Rev]}]),
+ ?assertEqual([], PRevs),
+
+ {ok, Db4} = couch_db:reopen(Db3),
+ {ok, PIdsRevs} = couch_db:fold_purged_docs(Db4, 0, fun fold_fun/2, [], []),
+ ?assertEqual(1, couch_db_engine:get_doc_count(Db4)),
+ ?assertEqual(2, couch_db_engine:get_update_seq(Db4)),
+ ?assertEqual(0, couch_db_engine:get_purge_seq(Db4)),
+ ?assertEqual([], PIdsRevs)
+ end).
+
+
+purge_conflicts(DbName) ->
+ ?_test(
+ begin
+ {ok, Db} = couch_db:open_int(DbName, []),
+ Doc = {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, <<"v1.1">>}]},
+ {ok, Rev} = save_doc(Db, Doc),
+ couch_db:ensure_full_commit(Db),
+ {ok, Db2} = couch_db:reopen(Db),
+
+ % create a conflict
+ DocConflict = #doc{
+ id = <<"foo">>,
+ revs = {1, [crypto:hash(md5, <<"v1.2">>)]},
+ body = {[ {<<"vsn">>, <<"v1.2">>}]}
+ },
+ {ok, _} = couch_db:update_doc(Db2, DocConflict, [], replicated_changes),
+ couch_db:ensure_full_commit(Db2),
+ {ok, Db3} = couch_db:reopen(Db2),
+
+ UUID = couch_uuids:new(),
+ {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db3,
+ [{UUID, <<"foo">>, [Rev]}]),
+ ?assertEqual([Rev], PRevs),
+
+ {ok, Db4} = couch_db:reopen(Db3),
+ {ok, PIdsRevs} = couch_db:fold_purged_docs(
+ Db4, 0, fun fold_fun/2, [], []),
+ % still has one doc
+ ?assertEqual(1, couch_db_engine:get_doc_count(Db4)),
+ ?assertEqual(0, couch_db_engine:get_del_doc_count(Db4)),
+ ?assertEqual(3, couch_db_engine:get_update_seq(Db4)),
+ ?assertEqual(1, couch_db_engine:get_purge_seq(Db4)),
+ ?assertEqual([{<<"foo">>, [Rev]}], PIdsRevs)
+ end).
+
+
+purge_deep_tree(DbName) ->
+ ?_test(
+ begin
+ NRevs = 100,
+ {ok, Db0} = couch_db:open_int(DbName, []),
+ Doc0 = {[{<<"_id">>, <<"bar">>}, {<<"vsn">>, 0}]},
+ {ok, InitRev} = save_doc(Db0, Doc0),
+ ok = couch_db:close(Db0),
+ LastRev = lists:foldl(fun(V, PrevRev) ->
+ {ok, Db} = couch_db:open_int(DbName, []),
+ {ok, Rev} = save_doc(Db,
+ {[{<<"_id">>, <<"bar">>},
+ {<<"vsn">>, V},
+ {<<"_rev">>, couch_doc:rev_to_str(PrevRev)}]}
+ ),
+ ok = couch_db:close(Db),
+ Rev
+ end, InitRev, lists:seq(2, NRevs)),
+ {ok, Db1} = couch_db:open_int(DbName, []),
+
+ % purge doc
+ UUID = couch_uuids:new(),
+ {ok, [{ok, PRevs}]} = couch_db:purge_docs(Db1,
+ [{UUID, <<"bar">>, [LastRev]}]),
+ ?assertEqual([LastRev], PRevs),
+
+ {ok, Db2} = couch_db:reopen(Db1),
+ % no docs left
+ ?assertEqual(0, couch_db_engine:get_doc_count(Db2)),
+ ?assertEqual(0, couch_db_engine:get_del_doc_count(Db2)),
+ ?assertEqual(1, couch_db_engine:get_purge_seq(Db2)),
+ ?assertEqual(NRevs + 1 , couch_db_engine:get_update_seq(Db2))
+ end).
+
+
+purge_with_replication() ->
+ ?_test(
+ begin
+ Ctx = test_util:start_couch([couch_replicator]),
+ Source = ?tempdb(),
+ {ok, SourceDb} = create_db(Source),
+ Target = ?tempdb(),
+ {ok, _Db} = create_db(Target),
+
+ % create Doc and do replication to Target
+ {ok, Rev} = save_doc(SourceDb,
+ {[{<<"_id">>, <<"foo">>}, {<<"vsn">>, 1}]}),
+ couch_db:ensure_full_commit(SourceDb),
+ {ok, SourceDb2} = couch_db:reopen(SourceDb),
+ RepObject = {[
+ {<<"source">>, Source},
+ {<<"target">>, Target}
+ ]},
+ {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
+ {ok, TargetDb} = couch_db:open_int(Target, []),
+ {ok, Doc} = couch_db:get_doc_info(TargetDb, <<"foo">>),
+
+ % purge Doc on Source and do replication to Target
+ % assert purges don't get replicated to Target
+ UUID = couch_uuids:new(),
+ {ok, _} = couch_db:purge_docs(SourceDb2, [{UUID, <<"foo">>, [Rev]}]),
+ {ok, SourceDb3} = couch_db:reopen(SourceDb2),
+ {ok, _} = couch_replicator:replicate(RepObject, ?ADMIN_USER),
+ {ok, TargetDb2} = couch_db:open_int(Target, []),
+ {ok, Doc2} = couch_db:get_doc_info(TargetDb2, <<"foo">>),
+ [Rev2] = Doc2#doc_info.revs,
+ ?assertEqual(Rev, Rev2#rev_info.rev),
+ ?assertEqual(Doc, Doc2),
+ ?assertEqual(0, couch_db_engine:get_doc_count(SourceDb3)),
+ ?assertEqual(1, couch_db_engine:get_purge_seq(SourceDb3)),
+ ?assertEqual(1, couch_db_engine:get_doc_count(TargetDb2)),
+ ?assertEqual(0, couch_db_engine:get_purge_seq(TargetDb2)),
+
+ % replicate from Target to Source
+ % assert that Doc reappears on Source
+ RepObject2 = {[
+ {<<"source">>, Target},
+ {<<"target">>, Source}
+ ]},
+ {ok, _} = couch_replicator:replicate(RepObject2, ?ADMIN_USER),
+ {ok, SourceDb4} = couch_db:reopen(SourceDb3),
+ {ok, Doc3} = couch_db:get_doc_info(SourceDb4, <<"foo">>),
+ [Rev3] = Doc3#doc_info.revs,
+ ?assertEqual(Rev, Rev3#rev_info.rev),
+ ?assertEqual(1, couch_db_engine:get_doc_count(SourceDb4)),
+ ?assertEqual(1, couch_db_engine:get_purge_seq(SourceDb4)),
+
+ delete_db(Source),
+ delete_db(Target),
+ ok = application:stop(couch_replicator),
+ ok = test_util:stop_couch(Ctx)
+ end).
+
+
+create_db(DbName) ->
+ couch_db:create(DbName, [?ADMIN_CTX, overwrite]).
+
+delete_db(DbName) ->
+ couch_server:delete(DbName, [?ADMIN_CTX]).
+
+save_doc(Db, Json) ->
+ Doc = couch_doc:from_json_obj(Json),
+ couch_db:update_doc(Db, Doc, []).
+
+fold_fun({_PSeq, _UUID, Id, Revs}, Acc) ->
+ [{Id, Revs} | Acc].
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
davisp@apache.org.