You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by kx...@apache.org on 2015/10/18 02:07:43 UTC
couch-replicator commit: updated refs/heads/master to e58f535
Repository: couchdb-couch-replicator
Updated Branches:
refs/heads/master 564224367 -> e58f535ca
Add a test case for filtered replication
This closes #10
Signed-off-by: Alexander Shorin <kx...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/commit/e58f535c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/tree/e58f535c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/diff/e58f535c
Branch: refs/heads/master
Commit: e58f535ca611acb40bb620ee2f6a3da4c187bcf7
Parents: 5642243
Author: ILYA Khlopotov <ii...@ca.ibm.com>
Authored: Fri May 22 06:04:39 2015 -0700
Committer: Alexander Shorin <kx...@apache.org>
Committed: Sun Oct 18 03:06:05 2015 +0300
----------------------------------------------------------------------
test/couch_replicator_filtered_tests.erl | 202 ++++++++++++++++++++++++++
1 file changed, 202 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch-replicator/blob/e58f535c/test/couch_replicator_filtered_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_replicator_filtered_tests.erl b/test/couch_replicator_filtered_tests.erl
new file mode 100644
index 0000000..c1a79e8
--- /dev/null
+++ b/test/couch_replicator_filtered_tests.erl
@@ -0,0 +1,202 @@
+% 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_replicator_filtered_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch_replicator/src/couch_replicator.hrl").
+
+-define(DDOC, {[
+ {<<"_id">>, <<"_design/filter_ddoc">>},
+ {<<"filters">>, {[
+ {<<"testfilter">>, <<"
+ function(doc, req){if (doc.class == 'mammal') return true;}
+ ">>}
+ ]}}
+]}).
+
+setup() ->
+ DbName = ?tempdb(),
+ {ok, Db} = couch_db:create(DbName, [?ADMIN_CTX]),
+ ok = couch_db:close(Db),
+ upload_ddoc(DbName),
+ create_docs(DbName),
+ DbName.
+
+setup(local) ->
+ setup();
+setup(remote) ->
+ {remote, setup()};
+setup({A, B}) ->
+ Ctx = test_util:start_couch([couch_replicator]),
+ Source = setup(A),
+ Target = setup(B),
+ {Ctx, {Source, Target}}.
+
+teardown({remote, DbName}) ->
+ teardown(DbName);
+teardown(DbName) ->
+ ok = couch_server:delete(DbName, [?ADMIN_CTX]),
+ ok.
+
+teardown(_, {Ctx, {Source, Target}}) ->
+ teardown(Source),
+ teardown(Target),
+ ok = application:stop(couch_replicator),
+ ok = test_util:stop_couch(Ctx).
+
+filtered_replication_test_() ->
+ Pairs = [{local, local}, {local, remote},
+ {remote, local}, {remote, remote}],
+ {
+ "Filtered replication tests",
+ {
+ foreachx,
+ fun setup/1, fun teardown/2,
+ [{Pair, fun should_succeed/2}
+ || Pair <- Pairs]
+ }
+ }.
+
+
+should_succeed({From, To}, {_Ctx, {Source, Target}}) ->
+ {ok, RepPid, RepId} = replicate(Source, Target),
+ %% FilteredFun is an Erlang version of following JS function
+ %% function(doc, req){if (doc.class == 'mammal') return true;}
+ FilterFun = fun(_DocId, {Props}) ->
+ couch_util:get_value(<<"class">>, Props) == <<"mammal">>
+ end,
+ {lists:flatten(io_lib:format("~p -> ~p", [From, To])),
+ {inorder, [
+ should_ensure_replication_runs(RepPid),
+ should_compare_databases(Source, Target, FilterFun),
+ should_cancel_replication(RepId, RepPid)
+ ]}}.
+
+should_ensure_replication_runs(RepPid) ->
+ ?_assert(begin
+ ?assertMatch(ok, wait_for_replicator(RepPid)),
+ is_process_alive(RepPid)
+ end).
+
+should_compare_databases({remote, Source}, Target, FilterFun) ->
+ should_compare_databases(Source, Target, FilterFun);
+should_compare_databases(Source, {remote, Target}, FilterFun) ->
+ should_compare_databases(Source, Target, FilterFun);
+should_compare_databases(Source, Target, FilterFun) ->
+ ?_assertEqual(1, begin
+ compare_dbs(Source, Target, FilterFun)
+ end).
+
+should_cancel_replication(RepId, RepPid) ->
+ ?_assertNot(begin
+ ?assertMatch({ok, _}, couch_replicator:cancel_replication(RepId)),
+ is_process_alive(RepPid)
+ end).
+
+compare_dbs(Source, Target, FilterFun) ->
+ {ok, SourceDb} = couch_db:open_int(Source, []),
+ {ok, TargetDb} = couch_db:open_int(Target, []),
+ Fun = fun(FullDocInfo, _, Acc) ->
+ {ok, DocId, SourceDoc} = read_doc(SourceDb, FullDocInfo, ?LINE),
+ case FilterFun(DocId, SourceDoc) of
+ true ->
+ {ok, DocId, TargetDoc} = read_doc(TargetDb, DocId, ?LINE),
+ ?assertEqual(SourceDoc, TargetDoc),
+ {ok, Acc + 1};
+ false ->
+ {ok, Acc}
+ end
+ end,
+ {ok, _, Acc} = couch_db:enum_docs(SourceDb, Fun, 0, []),
+ ok = couch_db:close(SourceDb),
+ ok = couch_db:close(TargetDb),
+ Acc.
+
+read_doc(Db, DocIdOrInfo, Line) ->
+ case couch_db:open_doc(Db, DocIdOrInfo) of
+ {ok, Doc} ->
+ {Props} = couch_doc:to_json_obj(Doc, [attachments]),
+ DocId = couch_util:get_value(<<"_id">>, Props),
+ {ok, DocId, {Props}};
+ Error ->
+ Reason = lists:concat([
+ "Error opening document '", ?b2l(doc_id(DocIdOrInfo)),
+ "' from target: ", couch_util:to_list(Error)]),
+ erlang:error(
+ {assertion_failed,
+ [{module, ?MODULE}, {line, Line},
+ {reason, Reason}]})
+ end.
+
+doc_id(#full_doc_info{id = Id}) -> Id;
+doc_id(Id) -> Id.
+
+
+wait_for_replicator(Pid) ->
+ %% since replicator started asynchronously
+ %% we need to wait when it would be in couch_task_status
+ %% we query replicator:details to ensure that do_init happen
+ ?assertMatch({ok, _}, couch_replicator:details(Pid)),
+ ok.
+
+create_docs(DbName) ->
+ {ok, Db} = couch_db:open(DbName, [?ADMIN_CTX]),
+ Doc1 = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"doc1">>},
+ {<<"class">>, <<"mammal">>},
+ {<<"value">>, 1}
+
+ ]}),
+ Doc2 = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"doc2">>},
+ {<<"value">>, 2}
+
+ ]}),
+ Doc3 = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"doc3">>},
+ {<<"class">>, <<"reptiles">>},
+ {<<"value">>, 3}
+
+ ]}),
+ {ok, _} = couch_db:update_docs(Db, [Doc1, Doc2, Doc3]),
+ couch_db:ensure_full_commit(Db),
+ couch_db:close(Db).
+
+
+upload_ddoc(DbName) ->
+ Url = db_url(DbName) ++ "/_design/filter_ddoc",
+ Body = couch_util:json_encode(?DDOC),
+ {ok, 201, _Resp, _Body} = test_request:put(Url, Body),
+ ok.
+
+replicate({remote, Db}, Target) ->
+ replicate(?l2b(db_url(Db)), Target);
+
+replicate(Source, {remote, Db}) ->
+ replicate(Source, ?l2b(db_url(Db)));
+
+replicate(Source, Target) ->
+ RepObject = {[
+ {<<"source">>, Source},
+ {<<"target">>, Target},
+ {<<"continuous">>, true}
+ ]},
+ {ok, Rep} = couch_replicator_utils:parse_rep_doc(RepObject, ?ADMIN_USER),
+ {ok, Pid} = couch_replicator:async_replicate(Rep),
+ {ok, Pid, Rep#rep.id}.
+
+db_url(DbName) ->
+ Addr = config:get("httpd", "bind_address", "127.0.0.1"),
+ Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
+ "http://" ++ Addr ++ ":" ++ Port ++ "/" ++ ?b2l(DbName).