You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ch...@apache.org on 2014/07/22 01:57:27 UTC
[14/43] couchdb commit: updated refs/heads/1963-eunit-bigcouch to
424dca5
Port 074-doc-update-conflicts.t etap test suite to eunit
Timeout decreased, added 10K clients case
Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/cb706216
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/cb706216
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/cb706216
Branch: refs/heads/1963-eunit-bigcouch
Commit: cb706216553a310ac84fcf0b33f850d3cc9c07b1
Parents: 7973c19
Author: Alexander Shorin <kx...@apache.org>
Authored: Wed May 21 18:58:12 2014 +0400
Committer: Russell Branca <ch...@apache.org>
Committed: Mon Jul 21 16:42:52 2014 -0700
----------------------------------------------------------------------
test/couchdb/couchdb_update_conflicts_tests.erl | 243 +++++++++++++++++++
test/etap/074-doc-update-conflicts.t | 208 ----------------
2 files changed, 243 insertions(+), 208 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb/blob/cb706216/test/couchdb/couchdb_update_conflicts_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb/couchdb_update_conflicts_tests.erl b/test/couchdb/couchdb_update_conflicts_tests.erl
new file mode 100644
index 0000000..7226860
--- /dev/null
+++ b/test/couchdb/couchdb_update_conflicts_tests.erl
@@ -0,0 +1,243 @@
+% 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(couchdb_update_conflicts_tests).
+
+-include("couch_eunit.hrl").
+-include_lib("couchdb/couch_db.hrl").
+
+-define(i2l(I), integer_to_list(I)).
+-define(ADMIN_USER, {userctx, #user_ctx{roles=[<<"_admin">>]}}).
+-define(DOC_ID, <<"foobar">>).
+-define(NUM_CLIENTS, [100, 500, 1000, 2000, 5000, 10000]).
+-define(TIMEOUT, 10000).
+
+
+start() ->
+ {ok, Pid} = couch_server_sup:start_link(?CONFIG_CHAIN),
+ couch_config:set("couchdb", "delayed_commits", "true", false),
+ Pid.
+
+stop(Pid) ->
+ erlang:monitor(process, Pid),
+ couch_server_sup:stop(),
+ receive
+ {'DOWN', _, _, Pid, _} ->
+ ok
+ after ?TIMEOUT ->
+ throw({timeout, server_stop})
+ end.
+
+setup() ->
+ DbName = ?tempdb(),
+ {ok, Db} = couch_db:create(DbName, [?ADMIN_USER, overwrite]),
+ Doc = couch_doc:from_json_obj({[{<<"_id">>, ?DOC_ID},
+ {<<"value">>, 0}]}),
+ {ok, Rev} = couch_db:update_doc(Db, Doc, []),
+ ok = couch_db:close(Db),
+ RevStr = couch_doc:rev_to_str(Rev),
+ {DbName, RevStr}.
+setup(_) ->
+ setup().
+
+teardown({DbName, _}) ->
+ ok = couch_server:delete(DbName, []),
+ ok.
+teardown(_, {DbName, _RevStr}) ->
+ teardown({DbName, _RevStr}).
+
+
+view_indexes_cleanup_test_() ->
+ {
+ "Update conflicts",
+ {
+ setup,
+ fun start/0, fun stop/1,
+ [
+ concurrent_updates(),
+ couchdb_188()
+ ]
+ }
+ }.
+
+concurrent_updates()->
+ {
+ "Concurrent updates",
+ {
+ foreachx,
+ fun setup/1, fun teardown/2,
+ [{NumClients, fun should_concurrently_update_doc/2}
+ || NumClients <- ?NUM_CLIENTS]
+ }
+ }.
+
+couchdb_188()->
+ {
+ "COUCHDB-188",
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [fun should_bulk_create_delete_doc/1]
+ }
+ }.
+
+
+should_concurrently_update_doc(NumClients, {DbName, InitRev})->
+ {?i2l(NumClients) ++ " clients",
+ {inorder,
+ [{"update doc",
+ {timeout, ?TIMEOUT div 1000,
+ ?_test(concurrent_doc_update(NumClients, DbName, InitRev))}},
+ {"ensure in single leaf",
+ ?_test(ensure_in_single_revision_leaf(DbName))}]}}.
+
+should_bulk_create_delete_doc({DbName, InitRev})->
+ ?_test(bulk_delete_create(DbName, InitRev)).
+
+
+concurrent_doc_update(NumClients, DbName, InitRev) ->
+ Clients = lists:map(
+ fun(Value) ->
+ ClientDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, ?DOC_ID},
+ {<<"_rev">>, InitRev},
+ {<<"value">>, Value}
+ ]}),
+ Pid = spawn_client(DbName, ClientDoc),
+ {Value, Pid, erlang:monitor(process, Pid)}
+ end,
+ lists:seq(1, NumClients)),
+
+ lists:foreach(fun({_, Pid, _}) -> Pid ! go end, Clients),
+
+ {NumConflicts, SavedValue} = lists:foldl(
+ fun({Value, Pid, MonRef}, {AccConflicts, AccValue}) ->
+ receive
+ {'DOWN', MonRef, process, Pid, {ok, _NewRev}} ->
+ {AccConflicts, Value};
+ {'DOWN', MonRef, process, Pid, conflict} ->
+ {AccConflicts + 1, AccValue};
+ {'DOWN', MonRef, process, Pid, Error} ->
+ erlang:error({assertion_failed,
+ [{module, ?MODULE},
+ {line, ?LINE},
+ {reason, "Client " ++ ?i2l(Value)
+ ++ " got update error: "
+ ++ couch_util:to_list(Error)}]})
+ after ?TIMEOUT div 2 ->
+ erlang:error({assertion_failed,
+ [{module, ?MODULE},
+ {line, ?LINE},
+ {reason, "Timeout waiting for client "
+ ++ ?i2l(Value) ++ " to die"}]})
+ end
+ end, {0, nil}, Clients),
+ ?assertEqual(NumClients - 1, NumConflicts),
+
+ {ok, Db} = couch_db:open_int(DbName, []),
+ {ok, Leaves} = couch_db:open_doc_revs(Db, ?DOC_ID, all, []),
+ ok = couch_db:close(Db),
+ ?assertEqual(1, length(Leaves)),
+
+ [{ok, Doc2}] = Leaves,
+ {JsonDoc} = couch_doc:to_json_obj(Doc2, []),
+ ?assertEqual(SavedValue, couch_util:get_value(<<"value">>, JsonDoc)).
+
+ensure_in_single_revision_leaf(DbName) ->
+ {ok, Db} = couch_db:open_int(DbName, []),
+ {ok, Leaves} = couch_db:open_doc_revs(Db, ?DOC_ID, all, []),
+ ok = couch_db:close(Db),
+ [{ok, Doc}] = Leaves,
+
+ %% FIXME: server restart won't work from test side
+ %% stop(ok),
+ %% start(),
+
+ {ok, Db2} = couch_db:open_int(DbName, []),
+ {ok, Leaves2} = couch_db:open_doc_revs(Db2, ?DOC_ID, all, []),
+ ok = couch_db:close(Db2),
+ ?assertEqual(1, length(Leaves2)),
+
+ [{ok, Doc2}] = Leaves,
+ ?assertEqual(Doc, Doc2).
+
+bulk_delete_create(DbName, InitRev) ->
+ {ok, Db} = couch_db:open_int(DbName, []),
+
+ DeletedDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, ?DOC_ID},
+ {<<"_rev">>, InitRev},
+ {<<"_deleted">>, true}
+ ]}),
+ NewDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, ?DOC_ID},
+ {<<"value">>, 666}
+ ]}),
+
+ {ok, Results} = couch_db:update_docs(Db, [DeletedDoc, NewDoc], []),
+ ok = couch_db:close(Db),
+
+ ?assertEqual(2, length([ok || {ok, _} <- Results])),
+ [{ok, Rev1}, {ok, Rev2}] = Results,
+
+ {ok, Db2} = couch_db:open_int(DbName, []),
+ {ok, [{ok, Doc1}]} = couch_db:open_doc_revs(
+ Db2, ?DOC_ID, [Rev1], [conflicts, deleted_conflicts]),
+ {ok, [{ok, Doc2}]} = couch_db:open_doc_revs(
+ Db2, ?DOC_ID, [Rev2], [conflicts, deleted_conflicts]),
+ ok = couch_db:close(Db2),
+
+ {Doc1Props} = couch_doc:to_json_obj(Doc1, []),
+ {Doc2Props} = couch_doc:to_json_obj(Doc2, []),
+
+ %% Document was deleted
+ ?assert(couch_util:get_value(<<"_deleted">>, Doc1Props)),
+ %% New document not flagged as deleted
+ ?assertEqual(undefined, couch_util:get_value(<<"_deleted">>,
+ Doc2Props)),
+ %% New leaf revision has the right value
+ ?assertEqual(666, couch_util:get_value(<<"value">>,
+ Doc2Props)),
+ %% Deleted document has no conflicts
+ ?assertEqual(undefined, couch_util:get_value(<<"_conflicts">>,
+ Doc1Props)),
+ %% Deleted document has no deleted conflicts
+ ?assertEqual(undefined, couch_util:get_value(<<"_deleted_conflicts">>,
+ Doc1Props)),
+ %% New leaf revision doesn't have conflicts
+ ?assertEqual(undefined, couch_util:get_value(<<"_conflicts">>,
+ Doc1Props)),
+ %% New leaf revision doesn't have deleted conflicts
+ ?assertEqual(undefined, couch_util:get_value(<<"_deleted_conflicts">>,
+ Doc1Props)),
+
+ %% Deleted revision has position 2
+ ?assertEqual(2, element(1, Rev1)),
+ %% New leaf revision has position 1
+ ?assertEqual(1, element(1, Rev2)).
+
+
+spawn_client(DbName, Doc) ->
+ spawn(fun() ->
+ {ok, Db} = couch_db:open_int(DbName, []),
+ receive
+ go -> ok
+ end,
+ erlang:yield(),
+ Result = try
+ couch_db:update_doc(Db, Doc, [])
+ catch _:Error ->
+ Error
+ end,
+ ok = couch_db:close(Db),
+ exit(Result)
+ end).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/cb706216/test/etap/074-doc-update-conflicts.t
----------------------------------------------------------------------
diff --git a/test/etap/074-doc-update-conflicts.t b/test/etap/074-doc-update-conflicts.t
deleted file mode 100755
index a7468e8..0000000
--- a/test/etap/074-doc-update-conflicts.t
+++ /dev/null
@@ -1,208 +0,0 @@
-#!/usr/bin/env escript
-%% -*- erlang -*-
-% 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.
-
--record(user_ctx, {
- name = null,
- roles = [],
- handler
-}).
-
--define(i2l(I), integer_to_list(I)).
-
-test_db_name() -> <<"couch_test_update_conflicts">>.
-
-
-main(_) ->
- test_util:run(25, fun() -> test() end).
-
-
-test() ->
- test_util:start_couch(),
- config:set("couchdb", "delayed_commits", "true", false),
-
- lists:foreach(
- fun(NumClients) -> test_concurrent_doc_update(NumClients) end,
- [100, 500, 1000]),
-
- test_bulk_delete_create(),
-
- ok.
-
-
-% Verify that if multiple clients try to update the same document
-% simultaneously, only one of them will get success response and all
-% the other ones will get a conflict error. Also validate that the
-% client which got the success response got its document version
-% persisted into the database.
-test_concurrent_doc_update(NumClients) ->
- {ok, Db} = create_db(test_db_name()),
- Doc = couch_doc:from_json_obj({[
- {<<"_id">>, <<"foobar">>},
- {<<"value">>, 0}
- ]}),
- {ok, Rev} = couch_db:update_doc(Db, Doc, []),
- ok = couch_db:close(Db),
- RevStr = couch_doc:rev_to_str(Rev),
- etap:diag("Created first revision of test document"),
-
- etap:diag("Spawning " ++ ?i2l(NumClients) ++
- " clients to update the document"),
- Clients = lists:map(
- fun(Value) ->
- ClientDoc = couch_doc:from_json_obj({[
- {<<"_id">>, <<"foobar">>},
- {<<"_rev">>, RevStr},
- {<<"value">>, Value}
- ]}),
- Pid = spawn_client(ClientDoc),
- {Value, Pid, erlang:monitor(process, Pid)}
- end,
- lists:seq(1, NumClients)),
-
- lists:foreach(fun({_, Pid, _}) -> Pid ! go end, Clients),
- etap:diag("Waiting for clients to finish"),
-
- {NumConflicts, SavedValue} = lists:foldl(
- fun({Value, Pid, MonRef}, {AccConflicts, AccValue}) ->
- receive
- {'DOWN', MonRef, process, Pid, {ok, _NewRev}} ->
- {AccConflicts, Value};
- {'DOWN', MonRef, process, Pid, conflict} ->
- {AccConflicts + 1, AccValue};
- {'DOWN', MonRef, process, Pid, Error} ->
- etap:bail("Client " ++ ?i2l(Value) ++
- " got update error: " ++ couch_util:to_list(Error))
- after 60000 ->
- etap:bail("Timeout waiting for client " ++ ?i2l(Value) ++ " to die")
- end
- end,
- {0, nil},
- Clients),
-
- etap:diag("Verifying client results"),
- etap:is(
- NumConflicts,
- NumClients - 1,
- "Got " ++ ?i2l(NumClients - 1) ++ " client conflicts"),
-
- {ok, Db2} = couch_db:open_int(test_db_name(), []),
- {ok, Leaves} = couch_db:open_doc_revs(Db2, <<"foobar">>, all, []),
- ok = couch_db:close(Db2),
- etap:is(length(Leaves), 1, "Only one document revision was persisted"),
- [{ok, Doc2}] = Leaves,
- {JsonDoc} = couch_doc:to_json_obj(Doc2, []),
- etap:is(
- couch_util:get_value(<<"value">>, JsonDoc),
- SavedValue,
- "Persisted doc has the right value"),
-
- ok = timer:sleep(1000),
- etap:diag("Restarting the server"),
- ok = test_util:stop_couch(),
- ok = timer:sleep(1000),
- ok = test_util:start_couch(),
-
- {ok, Db3} = couch_db:open_int(test_db_name(), []),
- {ok, Leaves2} = couch_db:open_doc_revs(Db3, <<"foobar">>, all, []),
- ok = couch_db:close(Db3),
- etap:is(length(Leaves2), 1, "Only one document revision was persisted"),
- [{ok, Doc3}] = Leaves,
- etap:is(Doc3, Doc2, "Got same document after server restart"),
-
- delete_db(Db3).
-
-
-% COUCHDB-188
-test_bulk_delete_create() ->
- {ok, Db} = create_db(test_db_name()),
- Doc = couch_doc:from_json_obj({[
- {<<"_id">>, <<"foobar">>},
- {<<"value">>, 0}
- ]}),
- {ok, Rev} = couch_db:update_doc(Db, Doc, []),
-
- DeletedDoc = couch_doc:from_json_obj({[
- {<<"_id">>, <<"foobar">>},
- {<<"_rev">>, couch_doc:rev_to_str(Rev)},
- {<<"_deleted">>, true}
- ]}),
- NewDoc = couch_doc:from_json_obj({[
- {<<"_id">>, <<"foobar">>},
- {<<"value">>, 666}
- ]}),
-
- {ok, Db2} = couch_db:reopen(Db),
- {ok, Results} = couch_db:update_docs(Db2, [DeletedDoc, NewDoc], []),
- ok = couch_db:close(Db2),
-
- etap:is(length([ok || {ok, _} <- Results]), 2,
- "Deleted and non-deleted versions got an ok reply"),
-
- [{ok, Rev1}, {ok, Rev2}] = Results,
- {ok, Db3} = couch_db:open_int(test_db_name(), []),
-
- {ok, [{ok, Doc1}]} = couch_db:open_doc_revs(
- Db3, <<"foobar">>, [Rev1], [conflicts, deleted_conflicts]),
- {ok, [{ok, Doc2}]} = couch_db:open_doc_revs(
- Db3, <<"foobar">>, [Rev2], [conflicts, deleted_conflicts]),
- ok = couch_db:close(Db3),
-
- {Doc1Props} = couch_doc:to_json_obj(Doc1, []),
- {Doc2Props} = couch_doc:to_json_obj(Doc2, []),
-
- etap:is(couch_util:get_value(<<"_deleted">>, Doc1Props), true,
- "Document was deleted"),
- etap:is(couch_util:get_value(<<"_deleted">>, Doc2Props), undefined,
- "New document not flagged as deleted"),
- etap:is(couch_util:get_value(<<"value">>, Doc2Props), 666,
- "New leaf revision has the right value"),
- etap:is(couch_util:get_value(<<"_conflicts">>, Doc1Props), undefined,
- "Deleted document has no conflicts"),
- etap:is(couch_util:get_value(<<"_deleted_conflicts">>, Doc1Props), undefined,
- "Deleted document has no deleted conflicts"),
- etap:is(couch_util:get_value(<<"_conflicts">>, Doc2Props), undefined,
- "New leaf revision doesn't have conflicts"),
- etap:is(couch_util:get_value(<<"_deleted_conflicts">>, Doc2Props), undefined,
- "New leaf revision doesn't have deleted conflicts"),
-
- etap:is(element(1, Rev1), 2, "Deleted revision has position 2"),
- etap:is(element(1, Rev2), 1, "New leaf revision has position 1"),
-
- delete_db(Db2).
-
-
-spawn_client(Doc) ->
- spawn(fun() ->
- {ok, Db} = couch_db:open_int(test_db_name(), []),
- receive go -> ok end,
- erlang:yield(),
- Result = try
- couch_db:update_doc(Db, Doc, [])
- catch _:Error ->
- Error
- end,
- ok = couch_db:close(Db),
- exit(Result)
- end).
-
-
-create_db(DbName) ->
- couch_db:create(
- DbName,
- [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}, overwrite]).
-
-
-delete_db(Db) ->
- ok = couch_server:delete(
- couch_db:name(Db), [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]).