couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rnew...@apache.org
Subject [47/50] [abbrv] git commit: updated refs/heads/1843-feature-bigcouch to cba2e81
Date Sat, 29 Jun 2013 15:18:28 GMT
Fix core etap tests


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/86ba6dbe
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/86ba6dbe
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/86ba6dbe

Branch: refs/heads/1843-feature-bigcouch
Commit: 86ba6dbe513875df50f7e3e35cbd5975c38307ed
Parents: 91b5d70
Author: Paul J. Davis <paul.joseph.davis@gmail.com>
Authored: Tue Mar 12 15:23:07 2013 -0500
Committer: Paul J. Davis <paul.joseph.davis@gmail.com>
Committed: Wed Mar 20 06:02:57 2013 -0500

----------------------------------------------------------------------
 share/www/script/test/oauth.js                  |   3 +-
 share/www/script/test/proxyauth.js              |   7 +-
 share/www/script/test/replication.js            |  20 +-
 share/www/script/test/replicator_db.js          |  11 +
 share/www/script/test/view_compaction.js        |  26 +-
 share/www/script/test/view_include_docs.js      |   2 +-
 share/www/script/test/view_update_seq.js        |  10 +-
 src/config/src/config_app.erl                   |  29 +-
 src/couch/src/couch_query_servers.erl           |  25 +-
 src/couch/src/couch_server.erl                  |   3 +-
 .../src/couch_replicator_manager.erl            | 351 ++++++++++++++-----
 test/etap/070-couch-db.t                        |   5 +-
 test/etap/071-couchdb-rapid-cycle.t             |   2 +-
 test/etap/072-cleanup.t                         |   2 +-
 test/etap/073-changes.t                         | 178 +++++-----
 test/etap/074-doc-update-conflicts.t            |  34 +-
 test/etap/075-auth-cache.t                      |   2 +-
 test/etap/076-file-compression.t                |   4 +-
 test/etap/077-couch-db-fast-db-delete-create.t  |  27 +-
 test/etap/080-config-get-set.t                  |   1 +
 test/etap/081-config-override.t                 |  14 +-
 test/etap/082-config-register.t                 |  55 ++-
 test/etap/121-stats-aggregates.t                |  13 +-
 test/etap/130-attachments-md5.t                 |   4 +-
 test/etap/140-attachment-comp.t                 |   4 +-
 test/etap/150-invalid-view-seq.t                |  19 +-
 test/etap/160-vhosts.t                          |   4 +-
 test/etap/171-os-daemons-config.t               |   4 +-
 test/etap/180-http-proxy.t                      |  14 +-
 test/etap/200-view-group-no-db-leaks.t          |   4 +-
 test/etap/201-view-group-shutdown.t             |   4 +-
 test/etap/210-os-proc-pool.t                    |   4 +-
 test/etap/220-compaction-daemon.t               |   4 +-
 test/etap/231-cors.t                            |   4 +-
 test/etap/test_util.erl.in                      |  31 +-
 35 files changed, 543 insertions(+), 381 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/oauth.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js
index 89d3779..96293f0 100644
--- a/share/www/script/test/oauth.js
+++ b/share/www/script/test/oauth.js
@@ -23,12 +23,14 @@ couchTests.oauth = function(debug) {
   var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
   var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"});
   var dbC = new CouchDB("test_suite_db_c", {"X-Couch-Full-Commit":"false"});
+  var dbD = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
   dbA.deleteDb();
   dbA.createDb();
   dbB.deleteDb();
   dbB.createDb();
   dbC.deleteDb();
   dbC.createDb();
+  dbD.deleteDb();
 
   // Simple secret key generator
   function generateSecret(length) {
@@ -124,7 +126,6 @@ couchTests.oauth = function(debug) {
         "X-Couch-Full-Commit":"false",
         "Authorization": adminBasicAuthHeaderValue()
       });
-      usersDb.deleteDb();
         
       // Create a user
       var jasonUserDoc = CouchDB.prepareUserDoc({

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/proxyauth.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/proxyauth.js b/share/www/script/test/proxyauth.js
index 016cec5..1677a66 100644
--- a/share/www/script/test/proxyauth.js
+++ b/share/www/script/test/proxyauth.js
@@ -19,7 +19,9 @@ couchTests.proxyauth = function(debug) {
   var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
   
   if (debug) debugger;
-  
+ 
+  usersDb.deleteDb();
+
   // Simple secret key generator
   function generateSecret(length) {
     var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -33,7 +35,6 @@ couchTests.proxyauth = function(debug) {
   var secret = generateSecret(64);
   
   function TestFun() {
-    usersDb.deleteDb();
     db.deleteDb();
     db.createDb();
     
@@ -126,4 +127,4 @@ couchTests.proxyauth = function(debug) {
     TestFun
   );
   
-};
\ No newline at end of file
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/replication.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js
index fd60dd4..7e4ecc1 100644
--- a/share/www/script/test/replication.js
+++ b/share/www/script/test/replication.js
@@ -1359,17 +1359,24 @@ couchTests.replication = function(debug) {
   TEquals(true, repResult.ok);
   TEquals('string', typeof repResult._local_id);
 
-  var xhr = CouchDB.request("GET", "/_active_tasks");
-  var tasks = JSON.parse(xhr.responseText);
-
   TEquals(true, sourceDb.compact().ok);
   while (sourceDb.info().compact_running) {};
 
   TEquals(true, sourceDb.save(makeDocs(30, 31)[0]).ok);
   xhr = CouchDB.request("GET", "/_active_tasks");
 
-  var tasksAfter = JSON.parse(xhr.responseText);
-  TEquals(tasks.length, tasksAfter.length);
+  var xhr = CouchDB.request("GET", "/_active_tasks");
+  var tasks = JSON.parse(xhr.responseText);
+
+  var found_task = false;
+  for(var i = 0; i < tasks.length; i++) {
+    if(tasks[i].replication_id == repResult._local_id) {
+      found_task = true;
+      break;
+    }
+  }
+  TEquals(true, found_task);
+
   waitForSeq(sourceDb, targetDb);
   T(sourceDb.open("30") !== null);
 
@@ -1695,6 +1702,9 @@ couchTests.replication = function(debug) {
   TEquals(true, repResult.ok);
   TEquals('string', typeof repResult._local_id);
 
+  // Race conditions are awesome
+  wait(500);
+
   xhr = CouchDB.request("GET", "/_active_tasks");
   tasks = JSON.parse(xhr.responseText);
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/replicator_db.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/replicator_db.js b/share/www/script/test/replicator_db.js
index edc85f4..230acd5 100644
--- a/share/www/script/test/replicator_db.js
+++ b/share/www/script/test/replicator_db.js
@@ -240,6 +240,8 @@ couchTests.replicator_db = function(debug) {
     T(copy !== null);
     T(copy.value === 1001);
 
+    wait(250);
+
     var repDoc1 = repDb.open(repDoc._id);
     T(repDoc1 !== null);
     T(repDoc1.source === repDoc.source);
@@ -276,6 +278,8 @@ couchTests.replicator_db = function(debug) {
     // stop replication by deleting the replication document
     T(repDb.deleteDoc(repDoc1).ok);
 
+    wait(200);
+
     // add another doc to source, it will NOT be replicated to target
     var docY = {
       _id: "foo666",
@@ -498,6 +502,8 @@ couchTests.replicator_db = function(debug) {
       T(copy.value === doc.value);
     }
 
+    wait(250);
+
     repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
     T(repDoc1 !== null);
     T(repDoc1._replication_state === "triggered");
@@ -542,6 +548,8 @@ couchTests.replicator_db = function(debug) {
 
     // deleting the 1st replication document stops the replication
     T(repDb.deleteDoc(repDoc1).ok);
+    wait(wait_rep_doc); //how to remove wait?
+
     var newDoc3 = {
         _id: "foo1983",
         value: 1983
@@ -655,6 +663,7 @@ couchTests.replicator_db = function(debug) {
       "GET", "/_config/replicator/db").responseText;
 
     repDb.deleteDb();
+    repDb.createDb();
 
     var xhr = CouchDB.request("PUT", "/_config/replicator/db", {
       body : JSON.stringify(repDb.name),
@@ -807,6 +816,8 @@ couchTests.replicator_db = function(debug) {
     TEquals("joe", CouchDB.session().userCtx.name);
     TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
 
+    wait(500);
+
     var repDoc = {
       _id: "foo_rep",
       source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/view_compaction.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/view_compaction.js b/share/www/script/test/view_compaction.js
index 151ce93..35d6276 100644
--- a/share/www/script/test/view_compaction.js
+++ b/share/www/script/test/view_compaction.js
@@ -27,7 +27,7 @@ couchTests.view_compaction = function(debug) {
         map: "function(doc) { emit(doc._id, doc.value) }"
       },
       view2: {
-        map: "function(doc) { emit(doc._id, doc.value); }",
+        map: "function(doc) { if (typeof(doc.integer) === 'number') {emit(doc._id, doc.integer);} }",
         reduce: "function(keys, values, rereduce) { return sum(values); }"
       }
     }
@@ -38,13 +38,13 @@ couchTests.view_compaction = function(debug) {
   db.bulkSave(docs);
 
   var resp = db.view('foo/view1', {});
-  T(resp.rows.length === 10000);
+  TEquals(10000, resp.rows.length);
 
   resp = db.view('foo/view2', {});
-  T(resp.rows.length === 1);
+  TEquals(1, resp.rows.length);
 
   resp = db.designInfo("_design/foo");
-  T(resp.view_index.update_seq === 10001);
+  TEquals(10001, resp.view_index.update_seq);
 
 
   // update docs
@@ -55,13 +55,13 @@ couchTests.view_compaction = function(debug) {
 
 
   resp = db.view('foo/view1', {});
-  T(resp.rows.length === 10000);
+  TEquals(10000, resp.rows.length);
 
   resp = db.view('foo/view2', {});
-  T(resp.rows.length === 1);
+  TEquals(1, resp.rows.length);
 
   resp = db.designInfo("_design/foo");
-  T(resp.view_index.update_seq === 20001);
+  TEquals(20001, resp.view_index.update_seq);
 
 
   // update docs again...
@@ -72,13 +72,13 @@ couchTests.view_compaction = function(debug) {
 
 
   resp = db.view('foo/view1', {});
-  T(resp.rows.length === 10000);
+  TEquals(10000, resp.rows.length);
 
   resp = db.view('foo/view2', {});
-  T(resp.rows.length === 1);
+  TEquals(1, resp.rows.length);
 
   resp = db.designInfo("_design/foo");
-  T(resp.view_index.update_seq === 30001);
+  TEquals(30001, resp.view_index.update_seq);
 
   var disk_size_before_compact = resp.view_index.disk_size;
   var data_size_before_compact = resp.view_index.data_size;
@@ -97,13 +97,13 @@ couchTests.view_compaction = function(debug) {
 
 
   resp = db.view('foo/view1', {});
-  T(resp.rows.length === 10000);
+  TEquals(10000, resp.rows.length);
 
   resp = db.view('foo/view2', {});
-  T(resp.rows.length === 1);
+  TEquals(1, resp.rows.length);
 
   resp = db.designInfo("_design/foo");
-  T(resp.view_index.update_seq === 30001);
+  TEquals(30001, resp.view_index.update_seq);
   T(resp.view_index.disk_size < disk_size_before_compact);
   TEquals("number", typeof resp.view_index.data_size, "data size is a number");
   T(resp.view_index.data_size < resp.view_index.disk_size, "data size < file size");

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/view_include_docs.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/view_include_docs.js b/share/www/script/test/view_include_docs.js
index 944c910..dab79b8 100644
--- a/share/www/script/test/view_include_docs.js
+++ b/share/www/script/test/view_include_docs.js
@@ -33,7 +33,7 @@ couchTests.view_include_docs = function(debug) {
         map: "function(doc) {if(doc.link_id) { var value = {'_id':doc.link_id}; if (doc.link_rev) {value._rev = doc.link_rev}; emit(doc._id, value);}};"
       },
       summate: {
-        map:"function (doc) {emit(doc.integer, doc.integer)};",
+        map:"function (doc) { if (typeof doc.integer === 'number') {emit(doc.integer, doc.integer)};}",
         reduce:"function (keys, values) { return sum(values); };"
       }
     }

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/share/www/script/test/view_update_seq.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/view_update_seq.js b/share/www/script/test/view_update_seq.js
index 69b8c42..df92b11 100644
--- a/share/www/script/test/view_update_seq.js
+++ b/share/www/script/test/view_update_seq.js
@@ -31,7 +31,7 @@ couchTests.view_update_seq = function(debug) {
         map: "function(doc) { emit(doc.integer, doc.string) }"
       },
       summate: {
-        map:"function (doc) {emit(doc.integer, doc.integer)};",
+        map:"function (doc) { if (typeof doc.integer === 'number') { emit(doc.integer, doc.integer)}; }",
         reduce:"function (keys, values) { return sum(values); };"
       }
     }
@@ -68,12 +68,12 @@ couchTests.view_update_seq = function(debug) {
   T(resp.rows.length == 1);
   T(resp.update_seq == 101);
 
-  db.save({"id":"0"});
+  db.save({"id":"0", "integer": 1});
   resp = db.view('test/all_docs', {limit: 1,stale: "ok", update_seq:true});
   T(resp.rows.length == 1);
   T(resp.update_seq == 101);
 
-  db.save({"id":"00"});
+  db.save({"id":"00", "integer": 2});
   resp = db.view('test/all_docs',
     {limit: 1, stale: "update_after", update_seq: true});
   T(resp.rows.length == 1);
@@ -100,7 +100,7 @@ couchTests.view_update_seq = function(debug) {
   resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
   T(resp.update_seq == 103);
 
-  resp = db.view('test/summate',{group:true, update_seq:true},["0","1"]);
-  T(resp.update_seq == 103);
+  resp = db.view('test/summate',{group:true, update_seq:true},[0,1]);
+  TEquals(103, resp.update_seq);
 
 };

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/src/config/src/config_app.erl
----------------------------------------------------------------------
diff --git a/src/config/src/config_app.erl b/src/config/src/config_app.erl
index 54f2433..5c5515a 100644
--- a/src/config/src/config_app.erl
+++ b/src/config/src/config_app.erl
@@ -28,14 +28,25 @@ stop(_State) ->
     ok.
 
 get_ini_files() ->
-    Etc = filename:join(code:root_dir(), "etc"),
-    Default = [filename:join(Etc,"default.ini"), filename:join(Etc,"local.ini")],
-    DefaultExists = lists:filter(fun filelib:is_file/1, Default),
+    hd([L || L <- [command_line(), env(), default()], L =/= skip]).
+
+env() ->
+    case application:get_env(config, ini_files) of
+        undefined ->
+            skip;
+        {ok, IniFiles} ->
+            IniFiles
+    end.
+
+command_line() ->
     case init:get_argument(couch_ini) of
-    error ->
-        DefaultExists;
-    {ok, [[]]} ->
-        DefaultExists;
-    {ok, [Values]} ->
-        Values
+        error ->
+            skip;
+        {ok, [IniFiles]} ->
+            IniFiles
     end.
+
+default() ->
+    Etc = filename:join(code:root_dir(), "etc"),
+    Default = [filename:join(Etc,"default.ini"), filename:join(Etc,"local.ini")],
+    lists:filter(fun filelib:is_file/1, Default).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/src/couch/src/couch_query_servers.erl
----------------------------------------------------------------------
diff --git a/src/couch/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl
index 8e4130e..4fef028 100644
--- a/src/couch/src/couch_query_servers.erl
+++ b/src/couch/src/couch_query_servers.erl
@@ -26,12 +26,12 @@
 -include_lib("couch/include/couch_db.hrl").
 
 -define(SUMERROR, <<"The _sum function requires that map values be numbers, "
-    "arrays of numbers, or objects. Objects cannot be mixed with other data "
-    "structures. Objects can be arbitrarily nested, provided that the values "
+    "arrays of numbers, or objects, not '~p'. Objects cannot be mixed with other "
+    "data structures. Objects can be arbitrarily nested, provided that the values "
     "for all fields are themselves numbers, arrays of numbers, or objects.">>).
 
 -define(STATERROR, <<"The _stats function requires that map values be numbers "
-    "or arrays of numbers.">>).
+    "or arrays of numbers, not '~p'">>).
 
 % https://gist.github.com/df10284c76d85f988c3f
 -define(SUMREGEX, {re_pattern,3,0,<<69,82,67,80,194,0,0,0,8,0,0,0,5,0,0,0,3,0,
@@ -238,8 +238,8 @@ sum_values(Value, Acc) when is_number(Value), is_list(Acc) ->
     sum_arrays(Acc, [Value]);
 sum_values(Value, Acc) when is_list(Value), is_number(Acc) ->
     sum_arrays([Acc], Value);
-sum_values(_Else, _Acc) ->
-    throw({invalid_value, ?SUMERROR}).
+sum_values(Else, _Acc) ->
+    throw_sum_error(Else).
 
 sum_objects([{K1, V1} | Rest1], [{K1, V2} | Rest2]) ->
     [{K1, sum_values(V1, V2)} | sum_objects(Rest1, Rest2)];
@@ -260,8 +260,8 @@ sum_arrays([], [_|_]=Ys) ->
     Ys;
 sum_arrays([X|Xs], [Y|Ys]) when is_number(X), is_number(Y) ->
     [X+Y | sum_arrays(Xs,Ys)];
-sum_arrays(_, _) ->
-    throw({invalid_value, ?SUMERROR}).
+sum_arrays(Else, _) ->
+    throw_sum_error(Else).
 
 builtin_stats(_, []) ->
     {[{sum,0}, {count,0}, {min,0}, {max,0}, {sumsqr,0}]};
@@ -288,8 +288,8 @@ stat_values(Value, Acc) when is_tuple(Value), is_tuple(Acc) ->
       erlang:max(Max0, Max1),
       Sqr0 + Sqr1
     };
-stat_values(_Else, _Acc) ->
-    throw({invalid_value, ?STATERROR}).
+stat_values(Else, _Acc) ->
+    throw_stat_error(Else).
 
 build_initial_accumulator(L) when is_list(L) ->
     [build_initial_accumulator(X) || X <- L];
@@ -445,6 +445,13 @@ ret_os_process(Proc) ->
     catch unlink(Proc#proc.pid),
     ok.
 
+throw_sum_error(Else) ->
+    throw({invalid_value, iolist_to_binary(io_lib:format(?SUMERROR, [Else]))}).
+
+throw_stat_error(Else) ->
+    throw({invalid_value, iolist_to_binary(io_lib:format(?STATERROR, [Else]))}).
+
+
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/src/couch/src/couch_server.erl
----------------------------------------------------------------------
diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl
index 80448b1..e4de69e 100644
--- a/src/couch/src/couch_server.erl
+++ b/src/couch/src/couch_server.erl
@@ -67,7 +67,8 @@ sup_start_link() ->
     gen_server:start_link({local, couch_server}, couch_server, [], []).
 
 
-open(DbName, Options) ->
+open(DbName, Options0) ->
+    Options = maybe_add_sys_db_callbacks(DbName, Options0),
     Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
     case ets:lookup(couch_dbs, DbName) of
     [#db{fd=Fd, fd_monitor=Lock} = Db] when Lock =/= locked ->

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/src/couch_replicator/src/couch_replicator_manager.erl
----------------------------------------------------------------------
diff --git a/src/couch_replicator/src/couch_replicator_manager.erl b/src/couch_replicator/src/couch_replicator_manager.erl
index c213199..047f573 100644
--- a/src/couch_replicator/src/couch_replicator_manager.erl
+++ b/src/couch_replicator/src/couch_replicator_manager.erl
@@ -17,6 +17,7 @@
 % public API
 -export([replication_started/1, replication_completed/2, replication_error/2]).
 
+-export([before_doc_update/2, after_doc_read/2]).
 
 % gen_server callbacks
 -export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]).
@@ -50,8 +51,6 @@
 }).
 
 -import(couch_util, [
-    get_value/2,
-    get_value/3,
     to_binary/1
 ]).
 
@@ -108,6 +107,9 @@ replication_error(#rep{id = {BaseId, _} = RepId}, Error) ->
     end.
 
 
+handle_config_change("replicator", "db", _, _, S) ->
+    ok = gen_server:call(S, rep_db_changed),
+    remove_handler;
 handle_config_change("replicator", "max_replication_retry_count", V, _, S) ->
     ok = gen_server:cast(S, {set_max_retries, retries_value(V)}),
     {ok, S};
@@ -124,11 +126,15 @@ init(_) ->
     Server = self(),
     ok = config:listen_for_changes(?MODULE, Server),
     ScanPid = spawn_link(fun() -> scan_all_dbs(Server) end),
+    % Automatically start node local changes feed loop
+    LocalRepDb = ?l2b(config:get("replicator", "db", "_replicator")),
+    Pid = changes_feed_loop(LocalRepDb, 0),
     {ok, #state{
         db_notifier = db_update_notifier(),
         scan_pid = ScanPid,
         max_retries = retries_value(
-            config:get("replicator", "max_replication_retry_count", "10"))
+            config:get("replicator", "max_replication_retry_count", "10")),
+        rep_start_pids = [Pid]
     }}.
 
 
@@ -137,8 +143,8 @@ handle_call({rep_db_update, DbName, {ChangeProps} = Change}, _From, State) ->
         process_update(State, DbName, Change)
     catch
     _Tag:Error ->
-        {RepProps} = get_value(doc, ChangeProps),
-        DocId = get_value(<<"_id">>, RepProps),
+        {RepProps} = get_json_value(doc, ChangeProps),
+        DocId = get_json_value(<<"_id">>, RepProps),
         rep_db_update_error(Error, DbName, DocId),
         State
     end,
@@ -180,6 +186,9 @@ handle_call({rep_db_checkpoint, DbName, EndSeq}, _From, State) ->
     true = ets:insert(?DB_TO_SEQ, {DbName, EndSeq}),
     {reply, ok, State};
 
+handle_call(rep_db_changed, _From, State) ->
+    {stop, shutdown, ok, State};
+
 handle_call(Msg, From, State) ->
     twig:log(error, "Replication manager received unexpected call ~p from ~p",
         [Msg, From]),
@@ -256,7 +265,7 @@ terminate(_Reason, State) ->
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
-changes_feed_loop(DbName, Since) ->
+changes_feed_loop(<<"shards/", _/binary>>=DbName, Since) ->
     Server = self(),
     Pid = spawn_link(
         fun() ->
@@ -286,11 +295,42 @@ changes_feed_loop(DbName, Since) ->
                 }
             )
         end),
-    Pid.
+    Pid;
+changes_feed_loop(DbName, Since) ->
+    ensure_rep_db_exists(DbName),
+    Server = self(),
+    spawn_link(fun() ->
+        UserCtx = #user_ctx{roles = [<<"_admin">>, <<"_replicator">>]},
+        DbOpenOptions = [{user_ctx, UserCtx}, sys_db],
+        {ok, Db} = couch_db:open_int(DbName, DbOpenOptions),
+        ChangesFeedFun = couch_changes:handle_changes(
+            #changes_args{
+                include_docs = true,
+                since = Since,
+                feed = "continuous",
+                timeout = infinity
+            },
+            {json_req, null},
+            Db
+        ),
+        EnumFun = fun
+        ({change, Change, _}, _) ->
+            case has_valid_rep_id(Change) of
+                true ->
+                    Msg = {rep_db_update, DbName, Change},
+                    ok = gen_server:call(Server, Msg, infinity);
+                false ->
+                    ok
+            end;
+        (_, _) ->
+            ok
+        end,
+        ChangesFeedFun(EnumFun)
+    end).
 
 
 has_valid_rep_id({Change}) ->
-    has_valid_rep_id(get_value(<<"id">>, Change));
+    has_valid_rep_id(get_json_value(<<"id">>, Change));
 has_valid_rep_id(<<?DESIGN_DOC_PREFIX, _Rest/binary>>) ->
     false;
 has_valid_rep_id(_Else) ->
@@ -299,22 +339,24 @@ has_valid_rep_id(_Else) ->
 db_update_notifier() ->
     Server = self(),
     IsReplicatorDbFun = is_replicator_db_fun(),
-    {ok, Notifier} = couch_db_update_notifier:start_link(
-        fun({updated, DbName}) ->
-            case IsReplicatorDbFun(DbName) of
-            true ->
-                ok = gen_server:call(Server, {resume_scan, mem3:dbname(DbName)}, infinity);
-            _ ->
-                ok
-            end;
-           ({deleted, DbName}) ->
-            case IsReplicatorDbFun(DbName) of
-            true ->
-                clean_up_replications(mem3:dbname(DbName));
-            _ ->
-                ok
+    {ok, Notifier} = couch_db_update_notifier:start_link(fun
+        ({Event, ShardDbName})
+                when Event == created; Event == updated; Event == deleted ->
+            DbName = mem3:dbname(ShardDbName),
+            IsRepDb = IsReplicatorDbFun(DbName),
+            case Event of
+                created when IsRepDb ->
+                    ensure_rep_ddoc_exists(DbName);
+                updated when IsRepDb ->
+                    ensure_rep_ddoc_exists(DbName),
+                    Msg = {resume_scan, DbName},
+                    ok = gen_server:call(Server, Msg, infinity);
+                deleted when IsRepDb ->
+                    clean_up_replications(DbName);
+                _ ->
+                    ok
             end;
-           (_) ->
+        (_Event) ->
             ok
         end
     ),
@@ -331,9 +373,9 @@ rescan(#state{scan_pid = ScanPid} = State) ->
     rescan(State#state{scan_pid = nil}).
 
 process_update(State, DbName, {Change}) ->
-    {RepProps} = JsonRepDoc = get_value(doc, Change),
-    DocId = get_value(<<"_id">>, RepProps),
-    case {mem3_util:owner(DbName, DocId), get_value(deleted, Change, false)} of
+    {RepProps} = JsonRepDoc = get_json_value(doc, Change),
+    DocId = get_json_value(<<"_id">>, RepProps),
+    case {is_owner(DbName, DocId), get_json_value(deleted, Change, false)} of
     {false, _} ->
         replication_complete(DbName, DocId),
         State;
@@ -341,7 +383,7 @@ process_update(State, DbName, {Change}) ->
         rep_doc_deleted(DbName, DocId),
         State;
     {true, false} ->
-        case get_value(<<"_replication_state">>, RepProps) of
+        case get_json_value(<<"_replication_state">>, RepProps) of
         undefined ->
             maybe_start_replication(State, DbName, DocId, JsonRepDoc);
         <<"triggered">> ->
@@ -359,6 +401,13 @@ process_update(State, DbName, {Change}) ->
         end
     end.
 
+
+is_owner(<<"shards/", _/binary>>=DbName, DocId) ->
+    mem3_util:owner(DbName, DocId);
+is_owner(_, _) ->
+    true.
+
+
 rep_db_update_error(Error, DbName, DocId) ->
     case Error of
     {bad_rep_doc, Reason} ->
@@ -372,13 +421,13 @@ rep_db_update_error(Error, DbName, DocId) ->
 
 
 rep_user_ctx({RepDoc}) ->
-    case get_value(<<"user_ctx">>, RepDoc) of
+    case get_json_value(<<"user_ctx">>, RepDoc) of
     undefined ->
         #user_ctx{};
     {UserCtx} ->
         #user_ctx{
-            name = get_value(<<"name">>, UserCtx, null),
-            roles = get_value(<<"roles">>, UserCtx, [])
+            name = get_json_value(<<"name">>, UserCtx, null),
+            roles = get_json_value(<<"roles">>, UserCtx, [])
         }
     end.
 
@@ -428,7 +477,7 @@ parse_rep_doc(RepDoc) ->
 
 
 maybe_tag_rep_doc(DbName, DocId, {RepProps}, RepId) ->
-    case get_value(<<"_replication_id">>, RepProps) of
+    case get_json_value(<<"_replication_id">>, RepProps) of
     RepId ->
         ok;
     _ ->
@@ -546,42 +595,26 @@ clean_up_replications(DbName) ->
 
 
 update_rep_doc(RepDbName, RepDocId, KVs) when is_binary(RepDocId) ->
-    {Pid, Ref} =
-    spawn_monitor(fun() ->
-        try
-            case fabric:open_doc(mem3:dbname(RepDbName), RepDocId, []) of
-                {ok, LatestRepDoc} ->
-                    update_rep_doc(RepDbName, LatestRepDoc, KVs);
-                _ ->
-                    ok
-            end
-        catch
+    try
+        case open_rep_doc(RepDbName, RepDocId) of
+            {ok, LastRepDoc} ->
+                update_rep_doc(RepDbName, LastRepDoc, KVs);
+            _ ->
+                ok
+        end
+    catch
         throw:conflict ->
-            % a race condition may cause an update conflict,
-            % in which cae update_rep_doc is called again to refetch
-            twig:log(error, "Conflict error when updating replication document `~s`."
-                         " Retrying.", [RepDocId]),
+            Msg = "Conflict when updating replication document `~s`. Retrying.",
+            twig:log(error, Msg, [RepDocId]),
             ok = timer:sleep(5),
-            update_rep_doc(RepDbName, RepDocId, KVs);
-        Type:Error ->
-            exit({Type, Error})
-        end
-    end),
-    receive
-    {'DOWN', Ref, process, Pid, normal} ->
-        ok;
-    {'DOWN', Ref, process, Pid, {throw, Error}} ->
-        throw(Error);
-    {'DOWN', Ref, process, Pid, {error, Error}} ->
-        erlang:error(Error)
+            update_rep_doc(RepDbName, RepDocId, KVs)
     end;
-
 update_rep_doc(RepDbName, #doc{body = {RepDocBody}} = RepDoc, KVs) ->
     NewRepDocBody = lists:foldl(
         fun({K, undefined}, Body) ->
                 lists:keydelete(K, 1, Body);
            ({<<"_replication_state">> = K, State} = KV, Body) ->
-                case get_value(K, Body) of
+                case get_json_value(K, Body) of
                 State ->
                     Body;
                 _ ->
@@ -600,9 +633,55 @@ update_rep_doc(RepDbName, #doc{body = {RepDocBody}} = RepDoc, KVs) ->
     _ ->
         % Might not succeed - when the replication doc is deleted right
         % before this update (not an error, ignore).
-        fabric:update_doc(RepDbName, RepDoc#doc{body = {NewRepDocBody}}, [?CTX])
+        save_rep_doc(RepDbName, RepDoc#doc{body = {NewRepDocBody}})
+    end.
+
+
+open_rep_doc(<<"shards/", _/binary>>=ShardDbName, DocId) ->
+    defer_call(fun() ->
+        fabric:open_doc(mem3:dbname(ShardDbName), DocId, [])
+    end);
+open_rep_doc(DbName, DocId) ->
+    {ok, Db} = couch_db:open_int(DbName, [?CTX, sys_db]),
+    try
+        couch_db:open_doc(Db, DocId, [ejson_body])
+    after
+        couch_db:close(Db)
+    end.
+
+save_rep_doc(<<"shards/", _/binary>>=DbName, Doc) ->
+    defer_call(fun() ->
+        fabric:update_doc(DbName, Doc, [?CTX])
+    end);
+save_rep_doc(DbName, Doc) ->
+    {ok, Db} = couch_db:open_int(DbName, [?CTX, sys_db]),
+    try
+        couch_db:update_doc(Db, Doc, [])
+    after
+        couch_db:close(Db)
     end.
 
+defer_call(Fun) ->
+    {Pid, Ref} = erlang:spawn_monitor(fun() ->
+        try
+            exit({exit_ok, Fun()})
+        catch
+            Type:Reason ->
+                exit({exit_err, Type, Reason})
+        end
+    end),
+    receive
+        {'DOWN', Ref, process, Pid, {exit_ok, Resp}} ->
+            Resp;
+        {'DOWN', Ref, process, Pid, {exit_err, throw, Error}} ->
+            throw(Error);
+        {'DOWN', Ref, process, Pid, {exit_err, error, Error}} ->
+            erlang:error(Error);
+        {'DOWN', Ref, process, Pid, {exit_err, exit, Error}} ->
+            exit(Error)
+    end.
+
+
 % RFC3339 timestamps.
 % Note: doesn't include the time seconds fraction (RFC3339 says it's optional).
 timestamp() ->
@@ -623,31 +702,38 @@ zone(Hr, Min) ->
     io_lib:format("-~2..0w:~2..0w", [abs(Hr), abs(Min)]).
 
 
-ensure_rep_db_exists() ->
-    DbName = ?l2b(couch_config:get("replicator", "db", "_replicator")),
-    UserCtx = #user_ctx{roles = [<<"_admin">>, <<"_replicator">>]},
-    case couch_db:open_int(DbName, [sys_db, {user_ctx, UserCtx}, nologifmissing]) of
-    {ok, Db} ->
-        Db;
-    _Error ->
-        {ok, Db} = couch_db:create(DbName, [sys_db, {user_ctx, UserCtx}])
+ensure_rep_db_exists(DbName) ->
+    Db = case couch_db:open_int(DbName, [?CTX, sys_db, nologifmissing]) of
+        {ok, Db0} ->
+            Db0;
+        _Error ->
+            {ok, Db0} = couch_db:create(DbName, [?CTX, sys_db]),
+            Db0
     end,
-    ensure_rep_ddoc_exists(Db, <<"_design/_replicator">>),
+    ensure_rep_ddoc_exists(DbName),
     {ok, Db}.
 
 
-ensure_rep_ddoc_exists(RepDb, DDocID) ->
-    case couch_db:open_doc(RepDb, DDocID, []) of
-    {ok, _Doc} ->
-        ok;
-    _ ->
-        DDoc = couch_doc:from_json_obj({[
-            {<<"_id">>, DDocID},
-            {<<"language">>, <<"javascript">>},
-            {<<"validate_doc_update">>, ?REP_DB_DOC_VALIDATE_FUN}
-        ]}),
-        {ok, _Rev} = couch_db:update_doc(RepDb, DDoc, [])
-     end.
+ensure_rep_ddoc_exists(RepDb) ->
+    DDocId = <<"_design/_replicator">>,
+    case open_rep_doc(RepDb, DDocId) of
+        {ok, _Doc} ->
+            ok;
+        _ ->
+            DDoc = couch_doc:from_json_obj({[
+                {<<"_id">>, DDocId},
+                {<<"language">>, <<"javascript">>},
+                {<<"validate_doc_update">>, ?REP_DB_DOC_VALIDATE_FUN}
+            ]}),
+            try
+                {ok, _} = save_rep_doc(RepDb, DDoc)
+            catch
+                throw:conflict ->
+                    % NFC what to do about this other than
+                    % not kill the process.
+                    ok
+            end
+    end.
 
 
 % pretty-print replication id
@@ -687,20 +773,85 @@ state_after_error(#rep_state{retries_left = Left, wait = Wait} = State) ->
         State#rep_state{retries_left = Left - 1, wait = Wait2}
     end.
 
+
+before_doc_update(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, _Db) ->
+    Doc;
+before_doc_update(#doc{body = {Body}} = Doc, #db{user_ctx=UserCtx} = Db) ->
+    #user_ctx{roles = Roles, name = Name} = UserCtx,
+    case lists:member(<<"_replicator">>, Roles) of
+    true ->
+        Doc;
+    false ->
+        case couch_util:get_value(?OWNER, Body) of
+        undefined ->
+            Doc#doc{body = {?replace(Body, ?OWNER, Name)}};
+        Name ->
+            Doc;
+        Other ->
+            case (catch couch_db:check_is_admin(Db)) of
+            ok when Other =:= null ->
+                Doc#doc{body = {?replace(Body, ?OWNER, Name)}};
+            ok ->
+                Doc;
+            _ ->
+                throw({forbidden, <<"Can't update replication documents",
+                    " from other users.">>})
+            end
+        end
+    end.
+
+
+after_doc_read(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, _Db) ->
+    Doc;
+after_doc_read(#doc{body = {Body}} = Doc, #db{user_ctx=UserCtx} = Db) ->
+    #user_ctx{name = Name} = UserCtx,
+    case (catch couch_db:check_is_admin(Db)) of
+    ok ->
+        Doc;
+    _ ->
+        case couch_util:get_value(?OWNER, Body) of
+        Name ->
+            Doc;
+        _Other ->
+            Source = strip_credentials(couch_util:get_value(<<"source">>,
+Body)),
+            Target = strip_credentials(couch_util:get_value(<<"target">>,
+Body)),
+            NewBody0 = ?replace(Body, <<"source">>, Source),
+            NewBody = ?replace(NewBody0, <<"target">>, Target),
+            #doc{revs = {Pos, [_ | Revs]}} = Doc,
+            NewDoc = Doc#doc{body = {NewBody}, revs = {Pos - 1, Revs}},
+            NewRevId = couch_db:new_revid(NewDoc),
+            NewDoc#doc{revs = {Pos, [NewRevId | Revs]}}
+        end
+    end.
+
+strip_credentials(Url) when is_binary(Url) ->
+    re:replace(Url,
+        "http(s)?://(?:[^:]+):[^@]+@(.*)$",
+        "http\\1://\\2",
+        [{return, binary}]);
+strip_credentials({Props}) ->
+    {lists:keydelete(<<"oauth">>, 1, Props)}.
+
 scan_all_dbs(Server) when is_pid(Server) ->
-    {ok, Db} = mem3_util:ensure_exists(
-        config:get("mem3", "shard_db", "dbs")),
+    {ok, Db} = mem3_util:ensure_exists(config:get("mem3", "shard_db", "dbs")),
     ChangesFun = couch_changes:handle_changes(#changes_args{}, nil, Db),
     IsReplicatorDbFun = is_replicator_db_fun(),
     ChangesFun(fun({change, {Change}, _}, _) ->
-        DbName = couch_util:get_value(<<"id">>, Change),
+        DbName = get_json_value(<<"id">>, Change),
         case DbName of <<"_design/", _/binary>> -> ok; _Else ->
             case couch_replicator_utils:is_deleted(Change) of
             true ->
                 ok;
             false ->
-                IsReplicatorDbFun(DbName) andalso
-                gen_server:call(Server, {resume_scan, DbName})
+                case IsReplicatorDbFun(DbName) of
+                    true ->
+                        ensure_rep_ddoc_exists(DbName),
+                        gen_server:call(Server, {resume_scan, DbName});
+                    false ->
+                        ok
+                end
             end
         end;
         (_, _) -> ok
@@ -709,6 +860,30 @@ scan_all_dbs(Server) when is_pid(Server) ->
 
 is_replicator_db_fun() ->
     {ok, RegExp} = re:compile("^([a-z][a-z0-9\\_\\$()\\+\\-\\/]*/)?_replicator$"),
-    fun(DbName) ->
-        match =:= re:run(mem3:dbname(DbName), RegExp, [{capture,none}])
+    fun
+        (<<"shards/", _/binary>>=DbName) ->
+            match =:= re:run(mem3:dbname(DbName), RegExp, [{capture,none}]);
+        (DbName) ->
+            LocalRepDb = ?l2b(config:get("replicator", "db", "_replicator")),
+            DbName == LocalRepDb
+    end.
+
+get_json_value(Key, Props) ->
+    get_json_value(Key, Props, undefined).
+
+get_json_value(Key, Props, Default) when is_atom(Key) ->
+    Ref = make_ref(),
+    case couch_util:get_value(Key, Props, Ref) of
+        Ref ->
+            couch_util:get_value(?l2b(atom_to_list(Key)), Props, Default);
+        Else ->
+            Else
+    end;
+get_json_value(Key, Props, Default) when is_binary(Key) ->
+    Ref = make_ref(),
+    case couch_util:get_value(Key, Props, Ref) of
+        Ref ->
+            couch_util:get_value(list_to_atom(?b2l(Key)), Props, Default);
+        Else ->
+            Else
     end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/070-couch-db.t
----------------------------------------------------------------------
diff --git a/test/etap/070-couch-db.t b/test/etap/070-couch-db.t
index 787d6c6..5fa9344 100755
--- a/test/etap/070-couch-db.t
+++ b/test/etap/070-couch-db.t
@@ -22,13 +22,13 @@ main(_) ->
             etap:end_tests();
         Other ->
             etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+            timer:sleep(1000),
             etap:bail(Other)
     end,
     ok.
 
 test() ->
-
-    couch_server_sup:start_link(test_util:config_files()),
+    test_util:start_couch(),
 
     couch_db:create(<<"etap-test-db">>, []),
     {ok, AllDbs} = couch_server:all_databases(),
@@ -70,4 +70,5 @@ test() ->
     end, 0, lists:seq(1, 6)),
     etap:is(6, NumDeleted, "Deleted all databases."),
 
+    ok = test_util:stop_couch(),
     ok.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/071-couchdb-rapid-cycle.t
----------------------------------------------------------------------
diff --git a/test/etap/071-couchdb-rapid-cycle.t b/test/etap/071-couchdb-rapid-cycle.t
index 80b0d72..2e86483 100755
--- a/test/etap/071-couchdb-rapid-cycle.t
+++ b/test/etap/071-couchdb-rapid-cycle.t
@@ -30,7 +30,7 @@ main(_) ->
 dbname() -> <<"etap-test-db">>.
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     timer:sleep(500),
 
     couch_server:delete(dbname(), []),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/072-cleanup.t
----------------------------------------------------------------------
diff --git a/test/etap/072-cleanup.t b/test/etap/072-cleanup.t
index 83b8a6b..4792377 100755
--- a/test/etap/072-cleanup.t
+++ b/test/etap/072-cleanup.t
@@ -40,7 +40,7 @@ main(_) ->
 
 test() ->
 
-    {ok, _} = couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     couch_server:delete(?TEST_DB, []),
     timer:sleep(1000),
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/073-changes.t
----------------------------------------------------------------------
diff --git a/test/etap/073-changes.t b/test/etap/073-changes.t
index 845cd79..2e70cc4 100755
--- a/test/etap/073-changes.t
+++ b/test/etap/073-changes.t
@@ -16,6 +16,8 @@
 % target of a replication doesn't affect the replication and that the
 % replication doesn't hold their reference counters forever.
 
+-mode(compile).
+
 -record(user_ctx, {
     name = null,
     roles = [],
@@ -49,21 +51,11 @@ test_db_name() -> <<"couch_test_changes">>.
 
 
 main(_) ->
-    test_util:init_code_path(),
-
-    etap:plan(43),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
-            etap:bail(Other)
-    end,
-    ok.
+    test_util:run(43, fun() -> test() end).
 
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    test_util:start_couch(),
 
     test_by_doc_ids(),
     test_by_doc_ids_with_since(),
@@ -71,22 +63,21 @@ test() ->
     test_design_docs_only(),
     test_heartbeat(),
 
-    couch_server_sup:stop(),
     ok.
 
 
 test_by_doc_ids() ->
-    {ok, Db} = create_db(test_db_name()),
-
-    {ok, _Rev1} = save_doc(Db, {[{<<"_id">>, <<"doc1">>}]}),
-    {ok, _Rev2} = save_doc(Db, {[{<<"_id">>, <<"doc2">>}]}),
-    {ok, Rev3} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}]}),
-    {ok, _Rev4} = save_doc(Db, {[{<<"_id">>, <<"doc4">>}]}),
-    {ok, _Rev5} = save_doc(Db, {[{<<"_id">>, <<"doc5">>}]}),
-    {ok, _Rev3_2} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3}]}),
-    {ok, _Rev6} = save_doc(Db, {[{<<"_id">>, <<"doc6">>}]}),
-    {ok, _Rev7} = save_doc(Db, {[{<<"_id">>, <<"doc7">>}]}),
-    {ok, _Rev8} = save_doc(Db, {[{<<"_id">>, <<"doc8">>}]}),
+    create_db(test_db_name()),
+
+    {ok, _Rev1} = save_doc({[{<<"_id">>, <<"doc1">>}]}),
+    {ok, _Rev2} = save_doc({[{<<"_id">>, <<"doc2">>}]}),
+    {ok, Rev3} = save_doc({[{<<"_id">>, <<"doc3">>}]}),
+    {ok, _Rev4} = save_doc({[{<<"_id">>, <<"doc4">>}]}),
+    {ok, _Rev5} = save_doc({[{<<"_id">>, <<"doc5">>}]}),
+    {ok, _Rev3_2} = save_doc({[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3}]}),
+    {ok, _Rev6} = save_doc({[{<<"_id">>, <<"doc6">>}]}),
+    {ok, _Rev7} = save_doc({[{<<"_id">>, <<"doc7">>}]}),
+    {ok, _Rev8} = save_doc({[{<<"_id">>, <<"doc8">>}]}),
 
     etap:diag("Folding changes in ascending order with _doc_ids filter"),
     ChangesArgs = #changes_args{
@@ -126,21 +117,21 @@ test_by_doc_ids() ->
     etap:is(Seq2_2, 4, "Second row has seq 6"),
 
     stop(Consumer2),
-    delete_db(Db).
+    delete_db().
 
 
 test_by_doc_ids_with_since() ->
-    {ok, Db} = create_db(test_db_name()),
-
-    {ok, _Rev1} = save_doc(Db, {[{<<"_id">>, <<"doc1">>}]}),
-    {ok, _Rev2} = save_doc(Db, {[{<<"_id">>, <<"doc2">>}]}),
-    {ok, Rev3} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}]}),
-    {ok, _Rev4} = save_doc(Db, {[{<<"_id">>, <<"doc4">>}]}),
-    {ok, _Rev5} = save_doc(Db, {[{<<"_id">>, <<"doc5">>}]}),
-    {ok, Rev3_2} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3}]}),
-    {ok, _Rev6} = save_doc(Db, {[{<<"_id">>, <<"doc6">>}]}),
-    {ok, _Rev7} = save_doc(Db, {[{<<"_id">>, <<"doc7">>}]}),
-    {ok, _Rev8} = save_doc(Db, {[{<<"_id">>, <<"doc8">>}]}),
+    create_db(test_db_name()),
+
+    {ok, _Rev1} = save_doc({[{<<"_id">>, <<"doc1">>}]}),
+    {ok, _Rev2} = save_doc({[{<<"_id">>, <<"doc2">>}]}),
+    {ok, Rev3} = save_doc({[{<<"_id">>, <<"doc3">>}]}),
+    {ok, _Rev4} = save_doc({[{<<"_id">>, <<"doc4">>}]}),
+    {ok, _Rev5} = save_doc({[{<<"_id">>, <<"doc5">>}]}),
+    {ok, Rev3_2} = save_doc({[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3}]}),
+    {ok, _Rev6} = save_doc({[{<<"_id">>, <<"doc6">>}]}),
+    {ok, _Rev7} = save_doc({[{<<"_id">>, <<"doc7">>}]}),
+    {ok, _Rev8} = save_doc({[{<<"_id">>, <<"doc8">>}]}),
 
     ChangesArgs = #changes_args{
         filter = "_doc_ids",
@@ -178,7 +169,6 @@ test_by_doc_ids_with_since() ->
     stop(Consumer2),
 
     {ok, _Rev3_3} = save_doc(
-        Db,
         {[{<<"_id">>, <<"doc3">>}, {<<"_deleted">>, true}, {<<"_rev">>, Rev3_2}]}),
 
     ChangesArgs3 = #changes_args{
@@ -200,21 +190,21 @@ test_by_doc_ids_with_since() ->
 
     stop(Consumer3),
 
-    delete_db(Db).
+    delete_db().
 
 
 test_by_doc_ids_continuous() ->
-    {ok, Db} = create_db(test_db_name()),
-
-    {ok, _Rev1} = save_doc(Db, {[{<<"_id">>, <<"doc1">>}]}),
-    {ok, _Rev2} = save_doc(Db, {[{<<"_id">>, <<"doc2">>}]}),
-    {ok, Rev3} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}]}),
-    {ok, Rev4} = save_doc(Db, {[{<<"_id">>, <<"doc4">>}]}),
-    {ok, _Rev5} = save_doc(Db, {[{<<"_id">>, <<"doc5">>}]}),
-    {ok, Rev3_2} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3}]}),
-    {ok, _Rev6} = save_doc(Db, {[{<<"_id">>, <<"doc6">>}]}),
-    {ok, _Rev7} = save_doc(Db, {[{<<"_id">>, <<"doc7">>}]}),
-    {ok, _Rev8} = save_doc(Db, {[{<<"_id">>, <<"doc8">>}]}),
+    create_db(test_db_name()),
+
+    {ok, _Rev1} = save_doc({[{<<"_id">>, <<"doc1">>}]}),
+    {ok, _Rev2} = save_doc({[{<<"_id">>, <<"doc2">>}]}),
+    {ok, Rev3} = save_doc({[{<<"_id">>, <<"doc3">>}]}),
+    {ok, Rev4} = save_doc({[{<<"_id">>, <<"doc4">>}]}),
+    {ok, _Rev5} = save_doc({[{<<"_id">>, <<"doc5">>}]}),
+    {ok, Rev3_2} = save_doc({[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3}]}),
+    {ok, _Rev6} = save_doc({[{<<"_id">>, <<"doc6">>}]}),
+    {ok, _Rev7} = save_doc({[{<<"_id">>, <<"doc7">>}]}),
+    {ok, _Rev8} = save_doc({[{<<"_id">>, <<"doc8">>}]}),
 
     ChangesArgs = #changes_args{
         filter = "_doc_ids",
@@ -235,17 +225,17 @@ test_by_doc_ids_continuous() ->
     etap:is(Seq2, 6, "Second row has seq 6"),
 
     clear_rows(Consumer),
-    {ok, _Rev9} = save_doc(Db, {[{<<"_id">>, <<"doc9">>}]}),
-    {ok, _Rev10} = save_doc(Db, {[{<<"_id">>, <<"doc10">>}]}),
+    {ok, _Rev9} = save_doc({[{<<"_id">>, <<"doc9">>}]}),
+    {ok, _Rev10} = save_doc({[{<<"_id">>, <<"doc10">>}]}),
     unpause(Consumer),
     pause(Consumer),
     etap:is(get_rows(Consumer), [], "No new rows"),
 
-    {ok, Rev4_2} = save_doc(Db, {[{<<"_id">>, <<"doc4">>}, {<<"_rev">>, Rev4}]}),
-    {ok, _Rev11} = save_doc(Db, {[{<<"_id">>, <<"doc11">>}]}),
-    {ok, _Rev4_3} = save_doc(Db, {[{<<"_id">>, <<"doc4">>}, {<<"_rev">>, Rev4_2}]}),
-    {ok, _Rev12} = save_doc(Db, {[{<<"_id">>, <<"doc12">>}]}),
-    {ok, Rev3_3} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3_2}]}),
+    {ok, Rev4_2} = save_doc({[{<<"_id">>, <<"doc4">>}, {<<"_rev">>, Rev4}]}),
+    {ok, _Rev11} = save_doc({[{<<"_id">>, <<"doc11">>}]}),
+    {ok, _Rev4_3} = save_doc({[{<<"_id">>, <<"doc4">>}, {<<"_rev">>, Rev4_2}]}),
+    {ok, _Rev12} = save_doc({[{<<"_id">>, <<"doc12">>}]}),
+    {ok, Rev3_3} = save_doc({[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3_2}]}),
     unpause(Consumer),
     pause(Consumer),
 
@@ -258,7 +248,7 @@ test_by_doc_ids_continuous() ->
     etap:is(Row16#row.id, <<"doc3">>, "Second row is for doc doc3"),
 
     clear_rows(Consumer),
-    {ok, _Rev3_4} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3_3}]}),
+    {ok, _Rev3_4} = save_doc({[{<<"_id">>, <<"doc3">>}, {<<"_rev">>, Rev3_3}]}),
     unpause(Consumer),
     pause(Consumer),
     etap:is(get_rows(Consumer), [#row{seq = 17, id = <<"doc3">>}],
@@ -266,15 +256,15 @@ test_by_doc_ids_continuous() ->
 
     unpause(Consumer),
     stop(Consumer),
-    delete_db(Db).
+    delete_db().
 
 
 test_design_docs_only() ->
-    {ok, Db} = create_db(test_db_name()),
+    create_db(test_db_name()),
 
-    {ok, _Rev1} = save_doc(Db, {[{<<"_id">>, <<"doc1">>}]}),
-    {ok, _Rev2} = save_doc(Db, {[{<<"_id">>, <<"doc2">>}]}),
-    {ok, Rev3} = save_doc(Db, {[{<<"_id">>, <<"_design/foo">>}]}),
+    {ok, _Rev1} = save_doc({[{<<"_id">>, <<"doc1">>}]}),
+    {ok, _Rev2} = save_doc({[{<<"_id">>, <<"doc2">>}]}),
+    {ok, Rev3} = save_doc({[{<<"_id">>, <<"_design/foo">>}]}),
 
     ChangesArgs = #changes_args{
         filter = "_design"
@@ -292,10 +282,7 @@ test_design_docs_only() ->
 
     stop(Consumer),
 
-    {ok, Db3} = couch_db:open_int(
-        test_db_name(), [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]),
     {ok, _Rev3_2} = save_doc(
-        Db3,
         {[{<<"_id">>, <<"_design/foo">>}, {<<"_rev">>, Rev3},
             {<<"_deleted">>, true}]}),
 
@@ -303,7 +290,6 @@ test_design_docs_only() ->
 
     {Rows2, LastSeq2} = wait_finished(Consumer2),
     UpSeq2 = UpSeq + 1,
-    couch_db:close(Db3),
 
     etap:is(LastSeq2, UpSeq2, "LastSeq is same as database update seq number"),
     etap:is(length(Rows2), 1, "Received 1 changes rows"),
@@ -313,12 +299,12 @@ test_design_docs_only() ->
         "Received row with deleted ddoc"),
 
     stop(Consumer2),
-    delete_db(Db).
+    delete_db().
 
 test_heartbeat() ->
-    {ok, Db} = create_db(test_db_name()),
+    create_db(test_db_name()),
 
-    {ok, _} = save_doc(Db, {[
+    {ok, _} = save_doc({[
         {<<"_id">>, <<"_design/foo">>},
         {<<"language">>, <<"javascript">>},
             {<<"filters">>, {[
@@ -340,49 +326,53 @@ test_heartbeat() ->
     },
     Consumer = spawn_consumer(test_db_name(), ChangesArgs, {json_req, null}),
 
-    {ok, _Rev1} = save_doc(Db, {[{<<"_id">>, <<"doc1">>}]}),
+    {ok, _Rev1} = save_doc({[{<<"_id">>, <<"doc1">>}]}),
     timer:sleep(200),
-    {ok, _Rev2} = save_doc(Db, {[{<<"_id">>, <<"doc2">>}]}),
+    {ok, _Rev2} = save_doc({[{<<"_id">>, <<"doc2">>}]}),
     timer:sleep(200),
-    {ok, _Rev3} = save_doc(Db, {[{<<"_id">>, <<"doc3">>}]}),
+    {ok, _Rev3} = save_doc({[{<<"_id">>, <<"doc3">>}]}),
     timer:sleep(200),
-    {ok, _Rev4} = save_doc(Db, {[{<<"_id">>, <<"doc4">>}]}),
+    {ok, _Rev4} = save_doc({[{<<"_id">>, <<"doc4">>}]}),
     timer:sleep(200),
-    {ok, _Rev5} = save_doc(Db, {[{<<"_id">>, <<"doc5">>}]}),
+    {ok, _Rev5} = save_doc({[{<<"_id">>, <<"doc5">>}]}),
     timer:sleep(200),
-    {ok, _Rev6} = save_doc(Db, {[{<<"_id">>, <<"doc6">>}]}),
+    {ok, _Rev6} = save_doc({[{<<"_id">>, <<"doc6">>}]}),
     timer:sleep(200),
-    {ok, _Rev7} = save_doc(Db, {[{<<"_id">>, <<"doc7">>}]}),
+    {ok, _Rev7} = save_doc({[{<<"_id">>, <<"doc7">>}]}),
     timer:sleep(200),
-    {ok, _Rev8} = save_doc(Db, {[{<<"_id">>, <<"doc8">>}]}),
+    {ok, _Rev8} = save_doc({[{<<"_id">>, <<"doc8">>}]}),
     timer:sleep(200),
-    {ok, _Rev9} = save_doc(Db, {[{<<"_id">>, <<"doc9">>}]}),
+    {ok, _Rev9} = save_doc({[{<<"_id">>, <<"doc9">>}]}),
     Heartbeats = get_heartbeats(Consumer),
     etap:is(Heartbeats, 2, "Received 2 heartbeats now"),
-    {ok, _Rev10} = save_doc(Db, {[{<<"_id">>, <<"doc10">>}]}),
+    {ok, _Rev10} = save_doc({[{<<"_id">>, <<"doc10">>}]}),
     timer:sleep(200),
-    {ok, _Rev11} = save_doc(Db, {[{<<"_id">>, <<"doc11">>}]}),
+    {ok, _Rev11} = save_doc({[{<<"_id">>, <<"doc11">>}]}),
     timer:sleep(200),
-    {ok, _Rev12} = save_doc(Db, {[{<<"_id">>, <<"doc12">>}]}),
+    {ok, _Rev12} = save_doc({[{<<"_id">>, <<"doc12">>}]}),
     Heartbeats2 = get_heartbeats(Consumer),
     etap:is(Heartbeats2, 3, "Received 3 heartbeats now"),
     Rows = get_rows(Consumer),
     etap:is(length(Rows), 3, "Received 3 changes rows"),
 
-    {ok, _Rev13} = save_doc(Db, {[{<<"_id">>, <<"doc13">>}]}),
+    {ok, _Rev13} = save_doc({[{<<"_id">>, <<"doc13">>}]}),
     timer:sleep(200),
-    {ok, _Rev14} = save_doc(Db, {[{<<"_id">>, <<"doc14">>}]}),
+    {ok, _Rev14} = save_doc({[{<<"_id">>, <<"doc14">>}]}),
     timer:sleep(200),
     Heartbeats3 = get_heartbeats(Consumer),
     etap:is(Heartbeats3, 6, "Received 6 heartbeats now"),
     stop(Consumer),
-    couch_db:close(Db),
-    delete_db(Db).
+    delete_db().
+
+
+db() ->
+    {ok, Db} = couch_db:reopen(get(current_db)),
+    Db.
 
 
-save_doc(Db, Json) ->
+save_doc(Json) ->
     Doc = couch_doc:from_json_obj(Json),
-    {ok, Rev} = couch_db:update_doc(Db, Doc, []),
+    {ok, Rev} = couch_db:update_doc(db(), Doc, []),
     {ok, couch_doc:rev_to_str(Rev)}.
 
 
@@ -547,11 +537,13 @@ stop_loop(Parent, Acc) ->
 
 
 create_db(DbName) ->
-    couch_db:create(
-        DbName,
-        [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}, overwrite]).
+    Options = [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}, overwrite],
+    {ok, Db} = couch_db:create(DbName, Options),
+    put(current_db, Db).
 
 
-delete_db(Db) ->
-    ok = couch_server:delete(
-        couch_db:name(Db), [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]).
+delete_db() ->
+    Db = db(),
+    Options = [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}],
+    ok = couch_server:delete(couch_db:name(Db), Options),
+    put(current_db, undefined).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/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
index 185a419..a7468e8 100755
--- a/test/etap/074-doc-update-conflicts.t
+++ b/test/etap/074-doc-update-conflicts.t
@@ -24,30 +24,19 @@ test_db_name() -> <<"couch_test_update_conflicts">>.
 
 
 main(_) ->
-    test_util:init_code_path(),
-
-    etap:plan(35),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
-            etap:bail(Other)
-    end,
-    ok.
+    test_util:run(25, fun() -> test() end).
 
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    test_util:start_couch(),
     config:set("couchdb", "delayed_commits", "true", false),
 
     lists:foreach(
         fun(NumClients) -> test_concurrent_doc_update(NumClients) end,
-        [100, 500, 1000, 2000, 5000]),
+        [100, 500, 1000]),
 
     test_bulk_delete_create(),
 
-    couch_server_sup:stop(),
     ok.
 
 
@@ -120,9 +109,9 @@ test_concurrent_doc_update(NumClients) ->
 
     ok = timer:sleep(1000),
     etap:diag("Restarting the server"),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok = timer:sleep(1000),
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
 
     {ok, Db3} = couch_db:open_int(test_db_name(), []),
     {ok, Leaves2} = couch_db:open_doc_revs(Db3, <<"foobar">>, all, []),
@@ -153,20 +142,21 @@ test_bulk_delete_create() ->
         {<<"value">>, 666}
     ]}),
 
-    {ok, Results} = couch_db:update_docs(Db, [DeletedDoc, NewDoc], []),
-    ok = couch_db:close(Db),
+    {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, Db2} = couch_db:open_int(test_db_name(), []),
+    {ok, Db3} = couch_db:open_int(test_db_name(), []),
 
     {ok, [{ok, Doc1}]} = couch_db:open_doc_revs(
-        Db2, <<"foobar">>, [Rev1], [conflicts, deleted_conflicts]),
+        Db3, <<"foobar">>, [Rev1], [conflicts, deleted_conflicts]),
     {ok, [{ok, Doc2}]} = couch_db:open_doc_revs(
-        Db2, <<"foobar">>, [Rev2], [conflicts, deleted_conflicts]),
-    ok = couch_db:close(Db2),
+        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, []),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/075-auth-cache.t
----------------------------------------------------------------------
diff --git a/test/etap/075-auth-cache.t b/test/etap/075-auth-cache.t
index 1b88858..1c8b181 100755
--- a/test/etap/075-auth-cache.t
+++ b/test/etap/075-auth-cache.t
@@ -65,7 +65,7 @@ main(_) ->
 
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    test_util:start_couch(),
     OrigName = config:get("couch_httpd_auth", "authentication_db"),
     config:set(
         "couch_httpd_auth", "authentication_db",

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/076-file-compression.t
----------------------------------------------------------------------
diff --git a/test/etap/076-file-compression.t b/test/etap/076-file-compression.t
index 5f75ad6..499aae5 100755
--- a/test/etap/076-file-compression.t
+++ b/test/etap/076-file-compression.t
@@ -12,6 +12,8 @@
 % License for the specific language governing permissions and limitations under
 % the License.
 
+-mode(compile).
+
 -record(user_ctx, {
     name = null,
     roles = [],
@@ -38,7 +40,7 @@ main(_) ->
 
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    test_util:start_couch(),
     config:set("couchdb", "file_compression", "none", false),
 
     create_database(),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/077-couch-db-fast-db-delete-create.t
----------------------------------------------------------------------
diff --git a/test/etap/077-couch-db-fast-db-delete-create.t b/test/etap/077-couch-db-fast-db-delete-create.t
index 2026698..4ba5c56 100644
--- a/test/etap/077-couch-db-fast-db-delete-create.t
+++ b/test/etap/077-couch-db-fast-db-delete-create.t
@@ -14,19 +14,8 @@
 % the License.
 
 main(_) ->
+    test_util:run(1, fun() -> test() end).
 
-    test_util:init_code_path(),
-
-    etap:plan(unknown),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            Msg = io_lib:format("Test died abnormally: ~p", [Other]),
-            etap:diag(Msg),
-            etap:bail(Msg)
-        end,
-    ok.
 
 loop(0) ->
     ok;
@@ -34,15 +23,17 @@ loop(N) ->
     ok = cycle(),
     loop(N - 1).
 
+
 cycle() ->
-    ok = couch_server:delete(<<"etap-test-db">>, []),
-    {ok, _Db} = couch_db:create(<<"etap-test-db">>, []),
+    {ok, Db} = couch_db:create(<<"etap-test-db">>, []),
+    % Dirty but only less dirty than importing the #db{} record
+    couch_file:close(element(5, Db)),
+    ok = couch_server:delete(<<"etap-test-db">>, [sync]),
     ok.
 
-test() ->
-    couch_server_sup:start_link(test_util:config_files()),
 
-    {ok, _Db} = couch_db:create(<<"etap-test-db">>, []),
+test() ->
+    test_util:start_couch(),
 
     ok = loop(1),
     ok = loop(10),
@@ -55,7 +46,5 @@ test() ->
     % ok = loop(1000000),
     % ok = loop(10000000),
 
-    ok = couch_server:delete(<<"etap-test-db">>, []),
-
     etap:is(true, true, "lots of creating and deleting of a database"),
     ok.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/080-config-get-set.t
----------------------------------------------------------------------
diff --git a/test/etap/080-config-get-set.t b/test/etap/080-config-get-set.t
index 6e5395e..d727936 100755
--- a/test/etap/080-config-get-set.t
+++ b/test/etap/080-config-get-set.t
@@ -29,6 +29,7 @@ main(_) ->
     ok.
 
 test() ->
+    application:set_env(config, ini_files, ["etc/couchdb/default_dev.ini"]),
     application:start(config),
 
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/081-config-override.t
----------------------------------------------------------------------
diff --git a/test/etap/081-config-override.t b/test/etap/081-config-override.t
index 2d47122..3a97b66 100755
--- a/test/etap/081-config-override.t
+++ b/test/etap/081-config-override.t
@@ -27,16 +27,12 @@ local_config_write() ->
 
 % Run tests and wait for the config gen_server to shutdown.
 run_tests(IniFiles, Tests) ->
-    application:start(config),
-    erlang:monitor(process, Pid),
+    application:set_env(config, ini_files, IniFiles),
+    ok = application:start(config),
     Tests(),
-    config:stop(),
-    receive
-        {'DOWN', _, _, Pid, _} -> ok;
-        _Other -> etap:diag("OTHER: ~p~n", [_Other])
-    after
-        1000 -> throw({timeout_error, config_stop})
-    end.
+    timer:sleep(1000),
+    ok = application:stop(config),
+    ok.
 
 main(_) ->
     test_util:init_code_path(),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/082-config-register.t
----------------------------------------------------------------------
diff --git a/test/etap/082-config-register.t b/test/etap/082-config-register.t
index 9487887..bd09ea7 100755
--- a/test/etap/082-config-register.t
+++ b/test/etap/082-config-register.t
@@ -13,6 +13,9 @@
 % License for the specific language governing permissions and limitations under
 % the License.
 
+-mode(compile).
+-export([handle_config_change/5]).
+
 default_config() ->
     test_util:build_file("etc/couchdb/default_dev.ini").
 
@@ -29,7 +32,8 @@ main(_) ->
     ok.
 
 test() ->
-    application:start(config),
+    application:set_env(config, ini_files, ["etc/couchdb/default_dev.ini"]),
+    ok = application:start(config),
 
     etap:is(
         config:get("httpd", "port"),
@@ -45,50 +49,31 @@ test() ->
         "{httpd, port} changed to 4895"
     ),
 
-    SentinelFunc = fun() ->
-        % Ping/Pong to make sure we wait for this
-        % process to die
-        receive {ping, From} -> From ! pong end
-    end,
-    SentinelPid = spawn(SentinelFunc),
-
-    config:register(
-        fun("httpd", "port", Value) ->
-            etap:is(Value, "8080", "Registered function got notification.")
-        end,
-        SentinelPid
-    ),
+    config:listen_for_changes(?MODULE, self()),
 
     ok = config:set("httpd", "port", "8080", false),
 
+    receive
+        {"httpd", "port", Value, false} ->
+            etap:is(Value, "8080", "Registered function got notification.")
+    after
+        1000 ->
+            etap:fail("notification failed")
+    end,
+
     % Implicitly checking that we *don't* call the function
     etap:is(
         config:get("httpd", "bind_address"),
         "127.0.0.1",
         "{httpd, bind_address} is not '0.0.0.0'"
     ),
-    ok = config:set("httpd", "bind_address", "0.0.0.0", false),
 
-    % Ping-Pong kill process
-    SentinelPid ! {ping, self()},
-    receive
-        _Any -> ok
-    after 1000 ->
-        throw({timeout_error, registered_pid})
-    end,
+    Msg = receive M -> M after 500 -> nil end,
+    etap:is(Msg, nil, "yay, no notification for get"),
 
-    ok = config:set("httpd", "port", "80", false),
-    etap:is(
-        config:get("httpd", "port"),
-        "80",
-        "Implicitly test that the function got de-registered"
-    ),
+    ok.
 
-    % test passing of Persist flag
-    config:register(
-        fun("httpd", _, _, Persist) ->
-            etap:is(Persist, false)
-        end),
-    ok = config:set("httpd", "port", "80", false),
+handle_config_change(Sec, Key, Val, Persist, Pid) ->
+    Pid ! {Sec, Key, Val, Persist},
+    {ok, Pid}.
 
-    ok.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/121-stats-aggregates.t
----------------------------------------------------------------------
diff --git a/test/etap/121-stats-aggregates.t b/test/etap/121-stats-aggregates.t
index 92bf547..4436beb 100755
--- a/test/etap/121-stats-aggregates.t
+++ b/test/etap/121-stats-aggregates.t
@@ -20,19 +20,10 @@ cfg_file() ->
     test_util:source_file("test/etap/121-stats-aggregates.cfg").
 
 main(_) ->
-    test_util:init_code_path(),
-    etap:plan(17),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
-            etap:bail()
-    end,
-    ok.
+    test_util:run(17, fun() -> test() end).
 
 test() ->
-    application:start(config),
+    config_sup:start_link([ini_file()]),
     couch_stats_collector:start(),
     couch_stats_aggregator:start(cfg_file()),
     ok = test_all_empty(),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/130-attachments-md5.t
----------------------------------------------------------------------
diff --git a/test/etap/130-attachments-md5.t b/test/etap/130-attachments-md5.t
index 12b1aba..fad057e 100755
--- a/test/etap/130-attachments-md5.t
+++ b/test/etap/130-attachments-md5.t
@@ -38,7 +38,7 @@ main(_) ->
     ok.
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     Addr = config:get("httpd", "bind_address", any),
     put(addr, Addr),
     put(port, mochiweb_socket_server:get(couch_httpd, port)),
@@ -59,7 +59,7 @@ test() ->
     test_chunked_with_invalid_md5_trailer(),
 
     couch_server:delete(test_db_name(), []),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 test_identity_without_md5() ->

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/140-attachment-comp.t
----------------------------------------------------------------------
diff --git a/test/etap/140-attachment-comp.t b/test/etap/140-attachment-comp.t
index 6e8640d..2f77956 100755
--- a/test/etap/140-attachment-comp.t
+++ b/test/etap/140-attachment-comp.t
@@ -30,7 +30,7 @@ main(_) ->
     ok.
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     put(addr, config:get("httpd", "bind_address", "127.0.0.1")),
     put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
     timer:sleep(1000),
@@ -75,7 +75,7 @@ test() ->
 
     timer:sleep(3000), % to avoid mochiweb socket closed exceptions
     couch_server:delete(test_db_name(), []),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 db_url() ->

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/150-invalid-view-seq.t
----------------------------------------------------------------------
diff --git a/test/etap/150-invalid-view-seq.t b/test/etap/150-invalid-view-seq.t
index 4553b16..a2ac22c 100755
--- a/test/etap/150-invalid-view-seq.t
+++ b/test/etap/150-invalid-view-seq.t
@@ -23,23 +23,13 @@ test_db_name() ->
     <<"couch_test_invalid_view_seq">>.
 
 main(_) ->
-    test_util:init_code_path(),
-
-    etap:plan(10),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
-            etap:bail(Other)
-    end,
-    ok.
+    test_util:run(10, fun() -> test() end).
 
 %% NOTE: since during the test we stop the server,
 %%       a huge and ugly but harmless stack trace is sent to stderr
 %%
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    test_util:start_couch(),
     timer:sleep(1000),
     delete_db(),
     create_db(),
@@ -62,7 +52,6 @@ test() ->
     query_view_after_restore_backup(),
 
     delete_db(),
-    couch_server_sup:stop(),
     ok.
 
 admin_user_ctx() ->
@@ -155,13 +144,13 @@ has_doc(DocId1, Rows) ->
     ).
 
 restore_backup_db_file() ->
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     timer:sleep(3000),
     DbFile = test_util:build_file("tmp/lib/" ++
         binary_to_list(test_db_name()) ++ ".couch"),
     ok = file:delete(DbFile),
     ok = file:rename(DbFile ++ ".backup", DbFile),
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     timer:sleep(1000),
     put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
     ok.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/160-vhosts.t
----------------------------------------------------------------------
diff --git a/test/etap/160-vhosts.t b/test/etap/160-vhosts.t
index 4118b94..c83d359 100755
--- a/test/etap/160-vhosts.t
+++ b/test/etap/160-vhosts.t
@@ -41,7 +41,7 @@ main(_) ->
     ok.
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     ibrowse:start(),
     crypto:start(),
 
@@ -119,7 +119,7 @@ test() ->
     couch_db:close(Db),
     ok = couch_server:delete(couch_db:name(Db), [admin_user_ctx()]),
     timer:sleep(3000),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
 
     ok.
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/171-os-daemons-config.t
----------------------------------------------------------------------
diff --git a/test/etap/171-os-daemons-config.t b/test/etap/171-os-daemons-config.t
index 8254c26..c8348d5 100755
--- a/test/etap/171-os-daemons-config.t
+++ b/test/etap/171-os-daemons-config.t
@@ -46,10 +46,8 @@ main(_) ->
     ok.
 
 test() ->
-    application:start(config),
+    test_util:start_couch(),
     config:set("log", "level", "debug", false),
-    couch_log:start_link(),
-    couch_os_daemons:start_link(),
 
     % "foo" is a required name by this test.
     config:set("os_daemons", "foo", daemon_cmd(), false),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/180-http-proxy.t
----------------------------------------------------------------------
diff --git a/test/etap/180-http-proxy.t b/test/etap/180-http-proxy.t
index 55f6aab..4897ad6 100755
--- a/test/etap/180-http-proxy.t
+++ b/test/etap/180-http-proxy.t
@@ -26,17 +26,7 @@ proxy() ->
 external() -> "https://www.google.com/".
 
 main(_) ->
-    test_util:init_code_path(),
-
-    etap:plan(61),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            etap:diag("Test died abnormally: ~p", [Other]),
-            etap:bail("Bad return value.")
-    end,
-    ok.
+    test_util:run(61, fun() -> test() end).
 
 check_request(Name, Req, Remote, Local) ->
     case Remote of
@@ -68,7 +58,7 @@ check_request(Name, Req, Remote, Local) ->
 
 test() ->
     ExtraConfig = [test_util:source_file("test/etap/180-http-proxy.ini")],
-    couch_server_sup:start_link(test_util:config_files() ++ ExtraConfig),
+    ok = test_util:start_couch(test_util:config_files() ++ ExtraConfig),
     ibrowse:start(),
     crypto:start(),
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/200-view-group-no-db-leaks.t
----------------------------------------------------------------------
diff --git a/test/etap/200-view-group-no-db-leaks.t b/test/etap/200-view-group-no-db-leaks.t
index f6ae3f0..4cf23b1 100755
--- a/test/etap/200-view-group-no-db-leaks.t
+++ b/test/etap/200-view-group-no-db-leaks.t
@@ -63,7 +63,7 @@ main(_) ->
     ok.
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     timer:sleep(1000),
     put(addr, config:get("httpd", "bind_address", "127.0.0.1")),
     put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
@@ -135,7 +135,7 @@ test() ->
 
     ok = timer:sleep(1000),
     delete_db(),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 admin_user_ctx() ->

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/201-view-group-shutdown.t
----------------------------------------------------------------------
diff --git a/test/etap/201-view-group-shutdown.t b/test/etap/201-view-group-shutdown.t
index fa77d89..f374888 100755
--- a/test/etap/201-view-group-shutdown.t
+++ b/test/etap/201-view-group-shutdown.t
@@ -64,7 +64,7 @@ main(_) ->
 
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     ok = config:set("couchdb", "max_dbs_open", "3", false),
     ok = config:set("couchdb", "delayed_commits", "false", false),
     crypto:start(),
@@ -73,7 +73,7 @@ test() ->
     % be closed by the database LRU system.
     test_view_group_compaction(),
 
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/210-os-proc-pool.t
----------------------------------------------------------------------
diff --git a/test/etap/210-os-proc-pool.t b/test/etap/210-os-proc-pool.t
index 85fa9a6..20c45e4 100755
--- a/test/etap/210-os-proc-pool.t
+++ b/test/etap/210-os-proc-pool.t
@@ -27,13 +27,13 @@ main(_) ->
 
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     config:set("query_server_config", "os_process_limit", "3", false),
 
     test_pool_full(),
     test_client_unexpected_exit(),
 
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/220-compaction-daemon.t
----------------------------------------------------------------------
diff --git a/test/etap/220-compaction-daemon.t b/test/etap/220-compaction-daemon.t
index 73bd5ac..262113d 100755
--- a/test/etap/220-compaction-daemon.t
+++ b/test/etap/220-compaction-daemon.t
@@ -36,7 +36,7 @@ main(_) ->
     ok.
 
 test() ->
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
     timer:sleep(1000),
     put(addr, config:get("httpd", "bind_address", "127.0.0.1")),
     put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))),
@@ -102,7 +102,7 @@ test() ->
     etap:is(couch_db:is_idle(Db), true, "Database is idle"),
 
     delete_db(),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 disable_compact_daemon() ->

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/231-cors.t
----------------------------------------------------------------------
diff --git a/test/etap/231-cors.t b/test/etap/231-cors.t
index cfc6a94..8e13c42 100644
--- a/test/etap/231-cors.t
+++ b/test/etap/231-cors.t
@@ -63,7 +63,7 @@ test() ->
     crypto:start(),
 
     %% launch couchdb
-    couch_server_sup:start_link(test_util:config_files()),
+    ok = test_util:start_couch(),
 
     %% initialize db
     timer:sleep(1000),
@@ -152,7 +152,7 @@ test() ->
     couch_server:delete(list_to_binary(dbname2()), [admin_user_ctx()]),
 
     timer:sleep(3000),
-    couch_server_sup:stop(),
+    ok = test_util:stop_couch(),
     ok.
 
 test_preflight_request() -> test_preflight_request(false).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/86ba6dbe/test/etap/test_util.erl.in
----------------------------------------------------------------------
diff --git a/test/etap/test_util.erl.in b/test/etap/test_util.erl.in
index 495909c..bdb7a17 100644
--- a/test/etap/test_util.erl.in
+++ b/test/etap/test_util.erl.in
@@ -16,6 +16,7 @@
 -export([source_file/1, build_file/1, config_files/0]).
 -export([run/2]).
 -export([request/3, request/4]).
+-export([start_couch/0, start_couch/1, stop_couch/0]).
 
 srcdir() ->
     "@abs_top_srcdir@".
@@ -54,13 +55,18 @@ config_files() ->
 run(Plan, Fun) ->
     test_util:init_code_path(),
     etap:plan(Plan),
-    case (catch Fun()) of
+    try Fun() of
         ok ->
             etap:end_tests();
-        Other ->
-            etap:diag(io_lib:format("Test died abnormally:~n~p", [Other])),
-            timer:sleep(500),
-            etap:bail(Other)
+        Else ->
+            etap:diag("Bad return:~n~p", [Else]),
+            timer:sleep(100),
+            etap:bail(Else)
+    catch Type:Reason ->
+        etap:diag("Test died: ~p~n  ~p~n  ~p~n",
+                [Type, Reason, erlang:get_stacktrace()]),
+        timer:sleep(1000),
+        etap:bail({Type, Reason})
     end,
     ok.
 
@@ -92,3 +98,18 @@ request(Url, Headers, Method, Body, N) ->
     Error ->
         Error
     end.
+
+start_couch() ->
+    start_couch(config_files()).
+
+start_couch(IniFiles) ->
+    ok = application:set_env(config, ini_files, IniFiles),
+    ok = application:start(config),
+    ok = application:start(twig),
+    ok = application:start(couch),
+    ok.
+
+stop_couch() ->
+    ok = application:stop(couch),
+    ok = application:stop(twig),
+    ok = application:stop(config).


Mime
View raw message