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/09/05 04:30:14 UTC

[35/50] [abbrv] Move files out of test/couchdb into top level test/ folder

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_key_tree_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_key_tree_tests.erl b/test/couch_key_tree_tests.erl
new file mode 100644
index 0000000..753ecc4
--- /dev/null
+++ b/test/couch_key_tree_tests.erl
@@ -0,0 +1,380 @@
+% 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_key_tree_tests).
+
+-include("couch_eunit.hrl").
+
+-define(DEPTH, 10).
+
+
+key_tree_merge_test_()->
+    {
+        "Key tree merge",
+        [
+            should_merge_with_empty_tree(),
+            should_merge_reflexive(),
+            should_merge_prefix_of_a_tree_with_tree(),
+            should_produce_conflict_on_merge_with_unrelated_branch(),
+            should_merge_reflexive_for_child_nodes(),
+            should_merge_tree_to_itself(),
+            should_merge_tree_of_odd_length(),
+            should_merge_tree_with_stem(),
+            should_merge_with_stem_at_deeper_level(),
+            should_merge_with_stem_at_deeper_level_with_deeper_paths(),
+            should_merge_single_tree_with_deeper_stem(),
+            should_merge_tree_with_large_stem(),
+            should_merge_stems(),
+            should_create_conflicts_on_merge(),
+            should_create_no_conflicts_on_merge(),
+            should_ignore_conflicting_branch()
+        ]
+    }.
+
+key_tree_missing_leaves_test_()->
+    {
+        "Missing tree leaves",
+        [
+            should_not_find_missing_leaves(),
+            should_find_missing_leaves()
+        ]
+    }.
+
+key_tree_remove_leaves_test_()->
+    {
+        "Remove tree leaves",
+        [
+            should_have_no_effect_on_removing_no_leaves(),
+            should_have_no_effect_on_removing_non_existant_branch(),
+            should_remove_leaf(),
+            should_produce_empty_tree_on_removing_all_leaves(),
+            should_have_no_effect_on_removing_non_existant_node(),
+            should_produce_empty_tree_on_removing_last_leaf()
+        ]
+    }.
+
+key_tree_get_leaves_test_()->
+    {
+        "Leaves retrieving",
+        [
+            should_extract_subtree(),
+            should_extract_subsubtree(),
+            should_gather_non_existant_leaf(),
+            should_gather_leaf(),
+            shoul_gather_multiple_leaves(),
+            should_retrieve_full_key_path(),
+            should_retrieve_full_key_path_for_node(),
+            should_retrieve_leaves_with_parent_node(),
+            should_retrieve_all_leaves()
+        ]
+    }.
+
+key_tree_leaf_counting_test_()->
+    {
+        "Leaf counting",
+        [
+            should_have_no_leaves_for_empty_tree(),
+            should_have_single_leaf_for_tree_with_single_node(),
+            should_have_two_leaves_for_tree_with_chindler_siblings(),
+            should_not_affect_on_leaf_counting_for_stemmed_tree()
+        ]
+    }.
+
+key_tree_stemming_test_()->
+    {
+        "Stemming",
+        [
+            should_have_no_effect_for_stemming_more_levels_than_exists(),
+            should_return_one_deepest_node(),
+            should_return_two_deepest_nodes()
+        ]
+    }.
+
+
+should_merge_with_empty_tree()->
+    One = {1, {"1","foo",[]}},
+    ?_assertEqual({[One], no_conflicts},
+                  couch_key_tree:merge([], One, ?DEPTH)).
+
+should_merge_reflexive()->
+    One = {1, {"1","foo",[]}},
+    ?_assertEqual({[One], no_conflicts},
+                  couch_key_tree:merge([One], One, ?DEPTH)).
+
+should_merge_prefix_of_a_tree_with_tree()->
+    One = {1, {"1","foo",[]}},
+    TwoSibs = [{1, {"1","foo",[]}},
+               {1, {"2","foo",[]}}],
+    ?_assertEqual({TwoSibs, no_conflicts},
+                  couch_key_tree:merge(TwoSibs, One, ?DEPTH)).
+
+should_produce_conflict_on_merge_with_unrelated_branch()->
+    TwoSibs = [{1, {"1","foo",[]}},
+               {1, {"2","foo",[]}}],
+    Three = {1, {"3","foo",[]}},
+    ThreeSibs = [{1, {"1","foo",[]}},
+                 {1, {"2","foo",[]}},
+                 {1, {"3","foo",[]}}],
+    ?_assertEqual({ThreeSibs, conflicts},
+                  couch_key_tree:merge(TwoSibs, Three, ?DEPTH)).
+
+should_merge_reflexive_for_child_nodes()->
+    TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
+    ?_assertEqual({[TwoChild], no_conflicts},
+                  couch_key_tree:merge([TwoChild], TwoChild, ?DEPTH)).
+
+should_merge_tree_to_itself()->
+    TwoChildSibs = {1, {"1","foo", [{"1a", "bar", []},
+                                    {"1b", "bar", []}]}},
+    ?_assertEqual({[TwoChildSibs], no_conflicts},
+                  couch_key_tree:merge([TwoChildSibs], TwoChildSibs, ?DEPTH)).
+
+should_merge_tree_of_odd_length()->
+    TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
+    TwoChildSibs = {1, {"1","foo", [{"1a", "bar", []},
+                                    {"1b", "bar", []}]}},
+    TwoChildPlusSibs = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]},
+                                        {"1b", "bar", []}]}},
+
+    ?_assertEqual({[TwoChildPlusSibs], no_conflicts},
+                  couch_key_tree:merge([TwoChild], TwoChildSibs, ?DEPTH)).
+
+should_merge_tree_with_stem()->
+    Stemmed = {2, {"1a", "bar", []}},
+    TwoChildSibs = {1, {"1","foo", [{"1a", "bar", []},
+                                    {"1b", "bar", []}]}},
+
+    ?_assertEqual({[TwoChildSibs], no_conflicts},
+                  couch_key_tree:merge([TwoChildSibs], Stemmed, ?DEPTH)).
+
+should_merge_with_stem_at_deeper_level()->
+    Stemmed = {3, {"1bb", "boo", []}},
+    TwoChildSibs = {1, {"1","foo", [{"1a", "bar", []},
+                                    {"1b", "bar", [{"1bb", "boo", []}]}]}},
+    ?_assertEqual({[TwoChildSibs], no_conflicts},
+                  couch_key_tree:merge([TwoChildSibs], Stemmed, ?DEPTH)).
+
+should_merge_with_stem_at_deeper_level_with_deeper_paths()->
+    Stemmed = {3, {"1bb", "boo", []}},
+    StemmedTwoChildSibs = [{2,{"1a", "bar", []}},
+                           {2,{"1b", "bar", [{"1bb", "boo", []}]}}],
+    ?_assertEqual({StemmedTwoChildSibs, no_conflicts},
+                  couch_key_tree:merge(StemmedTwoChildSibs, Stemmed, ?DEPTH)).
+
+should_merge_single_tree_with_deeper_stem()->
+    Stemmed = {3, {"1aa", "bar", []}},
+    TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
+    ?_assertEqual({[TwoChild], no_conflicts},
+                  couch_key_tree:merge([TwoChild], Stemmed, ?DEPTH)).
+
+should_merge_tree_with_large_stem()->
+    Stemmed = {2, {"1a", "bar", [{"1aa", "bar", []}]}},
+    TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
+    ?_assertEqual({[TwoChild], no_conflicts},
+                  couch_key_tree:merge([TwoChild], Stemmed, ?DEPTH)).
+
+should_merge_stems()->
+    StemmedA = {2, {"1a", "bar", [{"1aa", "bar", []}]}},
+    StemmedB = {3, {"1aa", "bar", []}},
+    ?_assertEqual({[StemmedA], no_conflicts},
+                  couch_key_tree:merge([StemmedA], StemmedB, ?DEPTH)).
+
+should_create_conflicts_on_merge()->
+    OneChild = {1, {"1","foo",[{"1a", "bar", []}]}},
+    Stemmed = {3, {"1aa", "bar", []}},
+    ?_assertEqual({[OneChild, Stemmed], conflicts},
+                  couch_key_tree:merge([OneChild], Stemmed, ?DEPTH)).
+
+should_create_no_conflicts_on_merge()->
+    OneChild = {1, {"1","foo",[{"1a", "bar", []}]}},
+    Stemmed = {3, {"1aa", "bar", []}},
+    TwoChild = {1, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
+    ?_assertEqual({[TwoChild], no_conflicts},
+                  couch_key_tree:merge([OneChild, Stemmed], TwoChild, ?DEPTH)).
+
+should_ignore_conflicting_branch()->
+    %% this test is based on couch-902-test-case2.py
+    %% foo has conflicts from replication at depth two
+    %% foo3 is the current value
+    Foo = {1, {"foo",
+               "val1",
+               [{"foo2","val2",[]},
+                {"foo3", "val3", []}
+               ]}},
+    %% foo now has an attachment added, which leads to foo4 and val4
+    %% off foo3
+    Bar = {1, {"foo",
+               [],
+               [{"foo3",
+                 [],
+                 [{"foo4","val4",[]}
+                  ]}]}},
+    %% this is what the merge returns
+    %% note that it ignore the conflicting branch as there's no match
+    FooBar = {1, {"foo",
+               "val1",
+               [{"foo2","val2",[]},
+                {"foo3", "val3", [{"foo4","val4",[]}]}
+               ]}},
+    {
+        "COUCHDB-902",
+        ?_assertEqual({[FooBar], no_conflicts},
+                      couch_key_tree:merge([Foo], Bar, ?DEPTH))
+    }.
+
+should_not_find_missing_leaves()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual([],
+                  couch_key_tree:find_missing(TwoChildSibs,
+                                              [{0,"1"}, {1,"1a"}])).
+
+should_find_missing_leaves()->
+    Stemmed1 = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
+    Stemmed2 = [{2, {"1aa", "bar", []}}],
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    [
+        ?_assertEqual(
+            [{0, "10"}, {100, "x"}],
+            couch_key_tree:find_missing(
+                TwoChildSibs,
+                [{0,"1"}, {0, "10"}, {1,"1a"}, {100, "x"}])),
+        ?_assertEqual(
+            [{0, "1"}, {100, "x"}],
+            couch_key_tree:find_missing(
+                Stemmed1,
+                [{0,"1"}, {1,"1a"}, {100, "x"}])),
+        ?_assertEqual(
+            [{0, "1"}, {1,"1a"}, {100, "x"}],
+            couch_key_tree:find_missing(
+                Stemmed2,
+                [{0,"1"}, {1,"1a"}, {100, "x"}]))
+    ].
+
+should_have_no_effect_on_removing_no_leaves()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({TwoChildSibs, []},
+                  couch_key_tree:remove_leafs(TwoChildSibs,
+                                              [])).
+
+should_have_no_effect_on_removing_non_existant_branch()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({TwoChildSibs, []},
+                  couch_key_tree:remove_leafs(TwoChildSibs,
+                                              [{0, "1"}])).
+
+should_remove_leaf()->
+    OneChild = [{0, {"1","foo",[{"1a", "bar", []}]}}],
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({OneChild, [{1, "1b"}]},
+                  couch_key_tree:remove_leafs(TwoChildSibs,
+                                              [{1, "1b"}])).
+
+should_produce_empty_tree_on_removing_all_leaves()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[], [{1, "1b"}, {1, "1a"}]},
+                  couch_key_tree:remove_leafs(TwoChildSibs,
+                                              [{1, "1b"}, {1, "1a"}])).
+
+should_have_no_effect_on_removing_non_existant_node()->
+    Stemmed = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
+    ?_assertEqual({Stemmed, []},
+                  couch_key_tree:remove_leafs(Stemmed,
+                                              [{1, "1a"}])).
+
+should_produce_empty_tree_on_removing_last_leaf()->
+    Stemmed = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
+    ?_assertEqual({[], [{2, "1aa"}]},
+                  couch_key_tree:remove_leafs(Stemmed,
+                                              [{2, "1aa"}])).
+
+should_extract_subtree()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[{"foo", {0, ["1"]}}],[]},
+                  couch_key_tree:get(TwoChildSibs, [{0, "1"}])).
+
+should_extract_subsubtree()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[{"bar", {1, ["1a", "1"]}}],[]},
+                  couch_key_tree:get(TwoChildSibs, [{1, "1a"}])).
+
+should_gather_non_existant_leaf()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[],[{0, "x"}]},
+                  couch_key_tree:get_key_leafs(TwoChildSibs, [{0, "x"}])).
+
+should_gather_leaf()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[{"bar", {1, ["1a","1"]}}],[]},
+                  couch_key_tree:get_key_leafs(TwoChildSibs, [{1, "1a"}])).
+
+shoul_gather_multiple_leaves()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[{"bar", {1, ["1a","1"]}},{"bar",{1, ["1b","1"]}}],[]},
+                  couch_key_tree:get_key_leafs(TwoChildSibs, [{0, "1"}])).
+
+should_retrieve_full_key_path()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[{0,[{"1", "foo"}]}],[]},
+                  couch_key_tree:get_full_key_paths(TwoChildSibs, [{0, "1"}])).
+
+should_retrieve_full_key_path_for_node()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual({[{1,[{"1a", "bar"},{"1", "foo"}]}],[]},
+                  couch_key_tree:get_full_key_paths(TwoChildSibs, [{1, "1a"}])).
+
+should_retrieve_leaves_with_parent_node()->
+    Stemmed = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    [
+        ?_assertEqual([{2, [{"1aa", "bar"},{"1a", "bar"}]}],
+                      couch_key_tree:get_all_leafs_full(Stemmed)),
+        ?_assertEqual([{1, [{"1a", "bar"},{"1", "foo"}]},
+                       {1, [{"1b", "bar"},{"1", "foo"}]}],
+                      couch_key_tree:get_all_leafs_full(TwoChildSibs))
+    ].
+
+should_retrieve_all_leaves()->
+    Stemmed = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    [
+        ?_assertEqual([{"bar", {2, ["1aa","1a"]}}],
+                      couch_key_tree:get_all_leafs(Stemmed)),
+        ?_assertEqual([{"bar", {1, ["1a", "1"]}}, {"bar", {1, ["1b","1"]}}],
+                      couch_key_tree:get_all_leafs(TwoChildSibs))
+    ].
+
+should_have_no_leaves_for_empty_tree()->
+    ?_assertEqual(0, couch_key_tree:count_leafs([])).
+
+should_have_single_leaf_for_tree_with_single_node()->
+    ?_assertEqual(1, couch_key_tree:count_leafs([{0, {"1","foo",[]}}])).
+
+should_have_two_leaves_for_tree_with_chindler_siblings()->
+    TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []}, {"1b", "bar", []}]}}],
+    ?_assertEqual(2, couch_key_tree:count_leafs(TwoChildSibs)).
+
+should_not_affect_on_leaf_counting_for_stemmed_tree()->
+    ?_assertEqual(1, couch_key_tree:count_leafs([{2, {"1bb", "boo", []}}])).
+
+should_have_no_effect_for_stemming_more_levels_than_exists()->
+    TwoChild = [{0, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}}],
+    ?_assertEqual(TwoChild, couch_key_tree:stem(TwoChild, 3)).
+
+should_return_one_deepest_node()->
+    TwoChild = [{0, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}}],
+    Stemmed = [{2, {"1aa", "bar", []}}],
+    ?_assertEqual(Stemmed, couch_key_tree:stem(TwoChild, 1)).
+
+should_return_two_deepest_nodes()->
+    TwoChild = [{0, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}}],
+    Stemmed = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
+    ?_assertEqual(Stemmed, couch_key_tree:stem(TwoChild, 2)).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_passwords_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_passwords_tests.erl b/test/couch_passwords_tests.erl
new file mode 100644
index 0000000..116265c
--- /dev/null
+++ b/test/couch_passwords_tests.erl
@@ -0,0 +1,54 @@
+% 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_passwords_tests).
+
+-include("couch_eunit.hrl").
+
+
+pbkdf2_test_()->
+    {"PBKDF2",
+     [
+         {"Iterations: 1, length: 20",
+          ?_assertEqual(
+              {ok, <<"0c60c80f961f0e71f3a9b524af6012062fe037a6">>},
+              couch_passwords:pbkdf2(<<"password">>, <<"salt">>, 1, 20))},
+
+         {"Iterations: 2, length: 20",
+          ?_assertEqual(
+              {ok, <<"ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957">>},
+              couch_passwords:pbkdf2(<<"password">>, <<"salt">>, 2, 20))},
+
+         {"Iterations: 4096, length: 20",
+          ?_assertEqual(
+              {ok, <<"4b007901b765489abead49d926f721d065a429c1">>},
+              couch_passwords:pbkdf2(<<"password">>, <<"salt">>, 4096, 20))},
+
+         {"Iterations: 4096, length: 25",
+          ?_assertEqual(
+              {ok, <<"3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038">>},
+              couch_passwords:pbkdf2(<<"passwordPASSWORDpassword">>,
+                                     <<"saltSALTsaltSALTsaltSALTsaltSALTsalt">>,
+                                     4096, 25))},
+         {"Null byte",
+          ?_assertEqual(
+              {ok, <<"56fa6aa75548099dcc37d7f03425e0c3">>},
+              couch_passwords:pbkdf2(<<"pass\0word">>,
+                                     <<"sa\0lt">>,
+                                     4096, 16))},
+
+         {timeout, 180,  %% this may runs too long on slow hosts
+          {"Iterations: 16777216 - this may take some time",
+           ?_assertEqual(
+               {ok, <<"eefe3d61cd4da4e4e9945b3d6ba2158c2634e984">>},
+               couch_passwords:pbkdf2(<<"password">>, <<"salt">>, 16777216, 20)
+           )}}]}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_ref_counter_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_ref_counter_tests.erl b/test/couch_ref_counter_tests.erl
new file mode 100644
index 0000000..b7e97b4
--- /dev/null
+++ b/test/couch_ref_counter_tests.erl
@@ -0,0 +1,107 @@
+% 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_ref_counter_tests).
+
+-include("couch_eunit.hrl").
+-include_lib("couchdb/couch_db.hrl").
+
+-define(TIMEOUT, 1000).
+
+
+setup() ->
+    {ok, RefCtr} = couch_ref_counter:start([]),
+    ChildPid = spawn(fun() -> loop() end),
+    {RefCtr, ChildPid}.
+
+teardown({_, ChildPid}) ->
+    erlang:monitor(process, ChildPid),
+    ChildPid ! close,
+    wait().
+
+
+couch_ref_counter_test_() ->
+    {
+        "CouchDB reference counter tests",
+        {
+            foreach,
+            fun setup/0, fun teardown/1,
+            [
+                fun should_initialize_with_calling_process_as_referrer/1,
+                fun should_ignore_unknown_pid/1,
+                fun should_increment_counter_on_pid_add/1,
+                fun should_not_increase_counter_on_readding_same_pid/1,
+                fun should_drop_ref_for_double_added_pid/1,
+                fun should_decrement_counter_on_pid_drop/1,
+                fun should_add_after_drop/1,
+                fun should_decrement_counter_on_process_exit/1
+
+            ]
+        }
+    }.
+
+
+should_initialize_with_calling_process_as_referrer({RefCtr, _}) ->
+    ?_assertEqual(1, couch_ref_counter:count(RefCtr)).
+
+should_ignore_unknown_pid({RefCtr, ChildPid}) ->
+    ?_assertEqual(ok, couch_ref_counter:drop(RefCtr, ChildPid)).
+
+should_increment_counter_on_pid_add({RefCtr, ChildPid}) ->
+    couch_ref_counter:add(RefCtr, ChildPid),
+    ?_assertEqual(2, couch_ref_counter:count(RefCtr)).
+
+should_not_increase_counter_on_readding_same_pid({RefCtr, ChildPid}) ->
+    couch_ref_counter:add(RefCtr, ChildPid),
+    couch_ref_counter:add(RefCtr, ChildPid),
+    ?_assertEqual(2, couch_ref_counter:count(RefCtr)).
+
+should_drop_ref_for_double_added_pid({RefCtr, ChildPid}) ->
+    couch_ref_counter:add(RefCtr, ChildPid),
+    couch_ref_counter:add(RefCtr, ChildPid),
+    couch_ref_counter:drop(RefCtr, ChildPid),
+    ?_assertEqual(2, couch_ref_counter:count(RefCtr)).
+
+should_decrement_counter_on_pid_drop({RefCtr, ChildPid}) ->
+    couch_ref_counter:add(RefCtr, ChildPid),
+    couch_ref_counter:drop(RefCtr, ChildPid),
+    ?_assertEqual(1, couch_ref_counter:count(RefCtr)).
+
+should_add_after_drop({RefCtr, ChildPid}) ->
+    couch_ref_counter:add(RefCtr, ChildPid),
+    couch_ref_counter:drop(RefCtr, ChildPid),
+    couch_ref_counter:add(RefCtr, ChildPid),
+    ?_assertEqual(2, couch_ref_counter:count(RefCtr)).
+
+should_decrement_counter_on_process_exit({RefCtr, ChildPid}) ->
+    ?_assertEqual(1,
+        begin
+            couch_ref_counter:add(RefCtr, ChildPid),
+            erlang:monitor(process, ChildPid),
+            ChildPid ! close,
+            wait(),
+            couch_ref_counter:count(RefCtr)
+        end).
+
+
+loop() ->
+    receive
+        close -> ok
+    end.
+
+wait() ->
+    receive
+        {'DOWN', _, _, _, _} ->
+            ok
+    after ?TIMEOUT ->
+        throw(timeout_error)
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_stats_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_stats_tests.erl b/test/couch_stats_tests.erl
new file mode 100644
index 0000000..d156449
--- /dev/null
+++ b/test/couch_stats_tests.erl
@@ -0,0 +1,412 @@
+% 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_stats_tests).
+
+-include("couch_eunit.hrl").
+-include_lib("couchdb/couch_db.hrl").
+
+-define(STATS_CFG_FIXTURE,
+    filename:join([?FIXTURESDIR, "couch_stats_aggregates.cfg"])).
+-define(STATS_INI_FIXTURE,
+    filename:join([?FIXTURESDIR, "couch_stats_aggregates.ini"])).
+-define(TIMEOUT, 1000).
+-define(TIMEWAIT, 500).
+
+
+setup_collector() ->
+    couch_stats_collector:start(),
+    ok.
+
+setup_aggregator(_) ->
+    {ok, Pid} = couch_config:start_link([?STATS_INI_FIXTURE]),
+    {ok, _} = couch_stats_collector:start(),
+    {ok, _} = couch_stats_aggregator:start(?STATS_CFG_FIXTURE),
+    Pid.
+
+teardown_collector(_) ->
+    couch_stats_collector:stop(),
+    ok.
+
+teardown_aggregator(_, Pid) ->
+    couch_stats_aggregator:stop(),
+    couch_stats_collector:stop(),
+    erlang:monitor(process, Pid),
+    couch_config:stop(),
+    receive
+        {'DOWN', _, _, Pid, _} ->
+            ok
+    after ?TIMEOUT ->
+        throw({timeout, config_stop})
+    end,
+    ok.
+
+
+couch_stats_collector_test_() ->
+    {
+        "CouchDB stats collector tests",
+        {
+            foreach,
+            fun setup_collector/0, fun teardown_collector/1,
+            [
+                should_increment_counter(),
+                should_decrement_counter(),
+                should_increment_and_decrement_counter(),
+                should_record_absolute_values(),
+                should_clear_absolute_values(),
+                should_track_process_count(),
+                should_increment_counter_multiple_times_per_pid(),
+                should_decrement_counter_on_process_exit(),
+                should_decrement_for_each_track_process_count_call_on_exit(),
+                should_return_all_counters_and_absolute_values(),
+                should_return_incremental_counters(),
+                should_return_absolute_values()
+            ]
+        }
+    }.
+
+couch_stats_aggregator_test_() ->
+    Funs = [
+        fun should_init_empty_aggregate/2,
+        fun should_get_empty_aggregate/2,
+        fun should_change_stats_on_values_add/2,
+        fun should_change_stats_for_all_times_on_values_add/2,
+        fun should_change_stats_on_values_change/2,
+        fun should_change_stats_for_all_times_on_values_change/2,
+        fun should_not_remove_data_after_some_time_for_0_sample/2,
+        fun should_remove_data_after_some_time_for_other_samples/2
+    ],
+    {
+        "CouchDB stats aggregator tests",
+        [
+            {
+                "Absolute values",
+                {
+                    foreachx,
+                    fun setup_aggregator/1, fun teardown_aggregator/2,
+                    [{absolute, Fun} || Fun <- Funs]
+                }
+            },
+            {
+                "Counters",
+                {
+                    foreachx,
+                    fun setup_aggregator/1, fun teardown_aggregator/2,
+                    [{counter, Fun} || Fun <- Funs]
+                }
+            }
+        ]
+    }.
+
+
+should_increment_counter() ->
+    ?_assertEqual(100,
+        begin
+            AddCount = fun() -> couch_stats_collector:increment(foo) end,
+            repeat(AddCount, 100),
+            couch_stats_collector:get(foo)
+        end).
+
+should_decrement_counter() ->
+    ?_assertEqual(67,
+        begin
+            AddCount = fun() -> couch_stats_collector:increment(foo) end,
+            RemCount = fun() -> couch_stats_collector:decrement(foo) end,
+            repeat(AddCount, 100),
+            repeat(RemCount, 33),
+            couch_stats_collector:get(foo)
+        end).
+
+should_increment_and_decrement_counter() ->
+    ?_assertEqual(0,
+        begin
+            AddCount = fun() -> couch_stats_collector:increment(foo) end,
+            RemCount = fun() -> couch_stats_collector:decrement(foo) end,
+            repeat(AddCount, 100),
+            repeat(RemCount, 25),
+            repeat(AddCount, 10),
+            repeat(RemCount, 5),
+            repeat(RemCount, 80),
+            couch_stats_collector:get(foo)
+        end).
+
+should_record_absolute_values() ->
+    ?_assertEqual(lists:seq(1, 15),
+        begin
+            lists:map(fun(Val) ->
+                couch_stats_collector:record(bar, Val)
+            end, lists:seq(1, 15)),
+            couch_stats_collector:get(bar)
+        end).
+
+should_clear_absolute_values() ->
+    ?_assertEqual(nil,
+        begin
+            lists:map(fun(Val) ->
+                couch_stats_collector:record(bar, Val)
+            end, lists:seq(1, 15)),
+            couch_stats_collector:clear(bar),
+            couch_stats_collector:get(bar)
+        end).
+
+should_track_process_count() ->
+    ?_assertMatch({_, 1}, spawn_and_count(1)).
+
+should_increment_counter_multiple_times_per_pid() ->
+    ?_assertMatch({_, 3}, spawn_and_count(3)).
+
+should_decrement_counter_on_process_exit() ->
+    ?_assertEqual(2,
+        begin
+            {Pid, 1} = spawn_and_count(1),
+            spawn_and_count(2),
+            RefMon = erlang:monitor(process, Pid),
+            Pid ! sepuku,
+            receive
+                {'DOWN', RefMon, _, _, _} -> ok
+            after ?TIMEOUT ->
+                throw(timeout)
+            end,
+            % sleep for awhile to let collector handle the updates
+            % suddenly, it couldn't notice process death instantly
+            timer:sleep(?TIMEWAIT),
+            couch_stats_collector:get(hoopla)
+        end).
+
+should_decrement_for_each_track_process_count_call_on_exit() ->
+    ?_assertEqual(2,
+        begin
+            {_, 2} = spawn_and_count(2),
+            {Pid, 6} = spawn_and_count(4),
+            RefMon = erlang:monitor(process, Pid),
+            Pid ! sepuku,
+            receive
+                {'DOWN', RefMon, _, _, _} -> ok
+            after ?TIMEOUT ->
+                throw(timeout)
+            end,
+            timer:sleep(?TIMEWAIT),
+            couch_stats_collector:get(hoopla)
+        end).
+
+should_return_all_counters_and_absolute_values() ->
+    ?_assertEqual([{bar,[1.0,0.0]}, {foo,1}],
+        begin
+            couch_stats_collector:record(bar, 0.0),
+            couch_stats_collector:record(bar, 1.0),
+            couch_stats_collector:increment(foo),
+            lists:sort(couch_stats_collector:all())
+        end).
+
+should_return_incremental_counters() ->
+    ?_assertEqual([{foo,1}],
+        begin
+            couch_stats_collector:record(bar, 0.0),
+            couch_stats_collector:record(bar, 1.0),
+            couch_stats_collector:increment(foo),
+            lists:sort(couch_stats_collector:all(incremental))
+        end).
+
+should_return_absolute_values() ->
+    ?_assertEqual([{bar,[1.0,0.0]}, {zing, "Z"}],
+        begin
+            couch_stats_collector:record(bar, 0.0),
+            couch_stats_collector:record(bar, 1.0),
+            couch_stats_collector:record(zing, 90),
+            couch_stats_collector:increment(foo),
+            lists:sort(couch_stats_collector:all(absolute))
+        end).
+
+should_init_empty_aggregate(absolute, _) ->
+    {Aggs} = couch_stats_aggregator:all(),
+    ?_assertEqual({[{'11', make_agg(<<"randomosity">>,
+                                    null, null, null, null, null)}]},
+                  couch_util:get_value(number, Aggs));
+should_init_empty_aggregate(counter, _) ->
+    {Aggs} = couch_stats_aggregator:all(),
+    ?_assertEqual({[{stuff, make_agg(<<"yay description">>,
+                                     null, null, null, null, null)}]},
+                  couch_util:get_value(testing, Aggs)).
+
+should_get_empty_aggregate(absolute, _) ->
+    ?_assertEqual(make_agg(<<"randomosity">>, null, null, null, null, null),
+             couch_stats_aggregator:get_json({number, '11'}));
+should_get_empty_aggregate(counter, _) ->
+    ?_assertEqual(make_agg(<<"yay description">>, null, null, null, null, null),
+             couch_stats_aggregator:get_json({testing, stuff})).
+
+should_change_stats_on_values_add(absolute, _) ->
+    lists:foreach(fun(X) ->
+        couch_stats_collector:record({number, 11}, X)
+    end, lists:seq(0, 10)),
+    couch_stats_aggregator:collect_sample(),
+    ?_assertEqual(make_agg(<<"randomosity">>, 5.0, 5.0, null, 5.0, 5.0),
+                  couch_stats_aggregator:get_json({number, 11}));
+should_change_stats_on_values_add(counter, _) ->
+    lists:foreach(fun(_) ->
+        couch_stats_collector:increment({testing, stuff})
+    end, lists:seq(1, 100)),
+    couch_stats_aggregator:collect_sample(),
+    ?_assertEqual(make_agg(<<"yay description">>, 100.0, 100.0, null, 100, 100),
+                  couch_stats_aggregator:get_json({testing, stuff})).
+
+should_change_stats_for_all_times_on_values_add(absolute, _) ->
+    lists:foreach(fun(X) ->
+        couch_stats_collector:record({number, 11}, X)
+    end, lists:seq(0, 10)),
+    couch_stats_aggregator:collect_sample(),
+    ?_assertEqual(make_agg(<<"randomosity">>, 5.0, 5.0, null, 5.0, 5.0),
+                  couch_stats_aggregator:get_json({number, 11}, 1));
+should_change_stats_for_all_times_on_values_add(counter, _) ->
+    lists:foreach(fun(_) ->
+        couch_stats_collector:increment({testing, stuff})
+    end, lists:seq(1, 100)),
+    couch_stats_aggregator:collect_sample(),
+    ?_assertEqual(make_agg(<<"yay description">>, 100.0, 100.0, null, 100, 100),
+                  couch_stats_aggregator:get_json({testing, stuff}, 1)).
+
+should_change_stats_on_values_change(absolute, _) ->
+    ?_assertEqual(make_agg(<<"randomosity">>, 20.0, 10.0, 7.071, 5.0, 15.0),
+        begin
+            lists:foreach(fun(X) ->
+                couch_stats_collector:record({number, 11}, X)
+            end, lists:seq(0, 10)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_collector:record({number, 11}, 15),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({number, 11})
+        end);
+should_change_stats_on_values_change(counter, _) ->
+    ?_assertEqual(make_agg(<<"yay description">>, 100.0, 50.0, 70.711, 0, 100),
+        begin
+            lists:foreach(fun(_) ->
+                couch_stats_collector:increment({testing, stuff})
+            end, lists:seq(1, 100)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({testing, stuff})
+        end).
+
+should_change_stats_for_all_times_on_values_change(absolute, _) ->
+    ?_assertEqual(make_agg(<<"randomosity">>, 20.0, 10.0, 7.071, 5.0, 15.0),
+        begin
+            lists:foreach(fun(X) ->
+                couch_stats_collector:record({number, 11}, X)
+            end, lists:seq(0, 10)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_collector:record({number, 11}, 15),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({number, 11}, 1)
+        end);
+should_change_stats_for_all_times_on_values_change(counter, _) ->
+    ?_assertEqual(make_agg(<<"yay description">>, 100.0, 50.0, 70.711, 0, 100),
+        begin
+            lists:foreach(fun(_) ->
+                couch_stats_collector:increment({testing, stuff})
+            end, lists:seq(1, 100)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({testing, stuff}, 1)
+        end).
+
+should_not_remove_data_after_some_time_for_0_sample(absolute, _) ->
+    ?_assertEqual(make_agg(<<"randomosity">>, 20.0, 10.0, 7.071, 5.0, 15.0),
+        begin
+            lists:foreach(fun(X) ->
+                couch_stats_collector:record({number, 11}, X)
+            end, lists:seq(0, 10)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_collector:record({number, 11}, 15),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({number, 11})
+        end);
+should_not_remove_data_after_some_time_for_0_sample(counter, _) ->
+    ?_assertEqual(make_agg(<<"yay description">>, 100.0, 33.333, 57.735, 0, 100),
+        begin
+            lists:foreach(fun(_) ->
+                couch_stats_collector:increment({testing, stuff})
+            end, lists:seq(1, 100)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({testing, stuff})
+        end).
+
+should_remove_data_after_some_time_for_other_samples(absolute, _) ->
+    ?_assertEqual(make_agg(<<"randomosity">>, 15.0, 15.0, null, 15.0, 15.0),
+        begin
+            lists:foreach(fun(X) ->
+                couch_stats_collector:record({number, 11}, X)
+            end, lists:seq(0, 10)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_collector:record({number, 11}, 15),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({number, 11}, 1)
+        end);
+should_remove_data_after_some_time_for_other_samples(counter, _) ->
+    ?_assertEqual(make_agg(<<"yay description">>, 0, 0.0, 0.0, 0, 0),
+        begin
+            lists:foreach(fun(_) ->
+                couch_stats_collector:increment({testing, stuff})
+            end, lists:seq(1, 100)),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            timer:sleep(?TIMEWAIT),
+            couch_stats_aggregator:collect_sample(),
+            couch_stats_aggregator:get_json({testing, stuff}, 1)
+        end).
+
+
+spawn_and_count(N) ->
+    Self = self(),
+    Pid = spawn(fun() ->
+        lists:foreach(
+            fun(_) ->
+                couch_stats_collector:track_process_count(hoopla)
+            end, lists:seq(1,N)),
+        Self ! reporting,
+        receive
+            sepuku -> ok
+        end
+    end),
+    receive reporting -> ok end,
+    {Pid, couch_stats_collector:get(hoopla)}.
+
+repeat(_, 0) ->
+    ok;
+repeat(Fun, Count) ->
+    Fun(),
+    repeat(Fun, Count-1).
+
+make_agg(Desc, Sum, Mean, StdDev, Min, Max) ->
+    {[
+        {description, Desc},
+        {current, Sum},
+        {sum, Sum},
+        {mean, Mean},
+        {stddev, StdDev},
+        {min, Min},
+        {max, Max}
+    ]}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_stream_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_stream_tests.erl b/test/couch_stream_tests.erl
new file mode 100644
index 0000000..335a2fe
--- /dev/null
+++ b/test/couch_stream_tests.erl
@@ -0,0 +1,100 @@
+% 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_stream_tests).
+
+-include("couch_eunit.hrl").
+
+
+setup() ->
+    {ok, Fd} = couch_file:open(?tempfile(), [create, overwrite]),
+    {ok, Stream} = couch_stream:open(Fd),
+    {Fd, Stream}.
+
+teardown({Fd, _}) ->
+    ok = couch_file:close(Fd).
+
+
+stream_test_() ->
+    {
+        "CouchDB stream tests",
+        {
+            foreach,
+            fun setup/0, fun teardown/1,
+            [
+                fun should_write/1,
+                fun should_write_consecutive/1,
+                fun should_write_empty_binary/1,
+                fun should_return_file_pointers_on_close/1,
+                fun should_return_stream_size_on_close/1,
+                fun should_return_valid_pointers/1,
+                fun should_recall_last_pointer_position/1,
+                fun should_stream_more_with_4K_chunk_size/1
+            ]
+        }
+    }.
+
+
+should_write({_, Stream}) ->
+    ?_assertEqual(ok, couch_stream:write(Stream, <<"food">>)).
+
+should_write_consecutive({_, Stream}) ->
+    couch_stream:write(Stream, <<"food">>),
+    ?_assertEqual(ok, couch_stream:write(Stream, <<"foob">>)).
+
+should_write_empty_binary({_, Stream}) ->
+    ?_assertEqual(ok, couch_stream:write(Stream, <<>>)).
+
+should_return_file_pointers_on_close({_, Stream}) ->
+    couch_stream:write(Stream, <<"foodfoob">>),
+    {Ptrs, _, _, _, _} = couch_stream:close(Stream),
+    ?_assertEqual([{0, 8}], Ptrs).
+
+should_return_stream_size_on_close({_, Stream}) ->
+    couch_stream:write(Stream, <<"foodfoob">>),
+    {_, Length, _, _, _} = couch_stream:close(Stream),
+    ?_assertEqual(8, Length).
+
+should_return_valid_pointers({Fd, Stream}) ->
+    couch_stream:write(Stream, <<"foodfoob">>),
+    {Ptrs, _, _, _, _} = couch_stream:close(Stream),
+    ?_assertEqual(<<"foodfoob">>, read_all(Fd, Ptrs)).
+
+should_recall_last_pointer_position({Fd, Stream}) ->
+    couch_stream:write(Stream, <<"foodfoob">>),
+    {_, _, _, _, _} = couch_stream:close(Stream),
+    {ok, ExpPtr} = couch_file:bytes(Fd),
+    {ok, Stream2} = couch_stream:open(Fd),
+    ZeroBits = <<0:(8 * 10)>>,
+    OneBits = <<1:(8 * 10)>>,
+    ok = couch_stream:write(Stream2, OneBits),
+    ok = couch_stream:write(Stream2, ZeroBits),
+    {Ptrs, 20, _, _, _} = couch_stream:close(Stream2),
+    [{ExpPtr, 20}] = Ptrs,
+    AllBits = iolist_to_binary([OneBits, ZeroBits]),
+    ?_assertEqual(AllBits, read_all(Fd, Ptrs)).
+
+should_stream_more_with_4K_chunk_size({Fd, _}) ->
+    {ok, Stream} = couch_stream:open(Fd, [{buffer_size, 4096}]),
+    lists:foldl(
+        fun(_, Acc) ->
+            Data = <<"a1b2c">>,
+            couch_stream:write(Stream, Data),
+            [Data | Acc]
+        end, [], lists:seq(1, 1024)),
+    ?_assertMatch({[{0, 4100}, {4106, 1020}], 5120, _, _, _},
+                  couch_stream:close(Stream)).
+
+
+read_all(Fd, PosList) ->
+    Data = couch_stream:foldl(Fd, PosList, fun(Bin, Acc) -> [Bin, Acc] end, []),
+    iolist_to_binary(Data).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_task_status_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_task_status_tests.erl b/test/couch_task_status_tests.erl
new file mode 100644
index 0000000..f71ad2b
--- /dev/null
+++ b/test/couch_task_status_tests.erl
@@ -0,0 +1,225 @@
+% 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_task_status_tests).
+
+-include("couch_eunit.hrl").
+-include_lib("couchdb/couch_db.hrl").
+
+-define(TIMEOUT, 1000).
+
+
+setup() ->
+    {ok, TaskStatusPid} = couch_task_status:start_link(),
+    TaskUpdaterPid = spawn(fun() -> loop() end),
+    {TaskStatusPid, TaskUpdaterPid}.
+
+teardown({TaskStatusPid, _}) ->
+    erlang:monitor(process, TaskStatusPid),
+    couch_task_status:stop(),
+    receive
+        {'DOWN', _, _, TaskStatusPid, _} ->
+            ok
+    after ?TIMEOUT ->
+        throw(timeout_error)
+    end.
+
+
+couch_task_status_test_() ->
+    {
+        "CouchDB task status updates",
+        {
+            foreach,
+            fun setup/0, fun teardown/1,
+            [
+                fun should_register_task/1,
+                fun should_set_task_startup_time/1,
+                fun should_have_update_time_as_startup_before_any_progress/1,
+                fun should_set_task_type/1,
+                fun should_not_register_multiple_tasks_for_same_pid/1,
+                fun should_set_task_progress/1,
+                fun should_update_task_progress/1,
+                fun should_update_time_changes_on_task_progress/1,
+                fun should_control_update_frequency/1,
+                fun should_reset_control_update_frequency/1,
+                fun should_track_multiple_tasks/1,
+                fun should_finish_task/1
+
+            ]
+        }
+    }.
+
+
+should_register_task({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    ?_assertEqual(1, length(couch_task_status:all())).
+
+should_set_task_startup_time({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    ?_assert(is_integer(get_task_prop(Pid, started_on))).
+
+should_have_update_time_as_startup_before_any_progress({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    StartTime = get_task_prop(Pid, started_on),
+    ?_assertEqual(StartTime, get_task_prop(Pid, updated_on)).
+
+should_set_task_type({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    ?_assertEqual(replication, get_task_prop(Pid, type)).
+
+should_not_register_multiple_tasks_for_same_pid({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    ?_assertEqual({add_task_error, already_registered},
+                  call(Pid, add, [{type, compaction}, {progress, 0}])).
+
+should_set_task_progress({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    ?_assertEqual(0, get_task_prop(Pid, progress)).
+
+should_update_task_progress({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    call(Pid, update, [{progress, 25}]),
+    ?_assertEqual(25, get_task_prop(Pid, progress)).
+
+should_update_time_changes_on_task_progress({_, Pid}) ->
+    ?_assert(
+        begin
+            ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+            ok = timer:sleep(1000),  % sleep awhile to customize update time
+            call(Pid, update, [{progress, 25}]),
+            get_task_prop(Pid, updated_on) > get_task_prop(Pid, started_on)
+        end).
+
+should_control_update_frequency({_, Pid}) ->
+    ?_assertEqual(66,
+        begin
+            ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+            call(Pid, update, [{progress, 50}]),
+            call(Pid, update_frequency, 500),
+            call(Pid, update, [{progress, 66}]),
+            call(Pid, update, [{progress, 77}]),
+            get_task_prop(Pid, progress)
+        end).
+
+should_reset_control_update_frequency({_, Pid}) ->
+    ?_assertEqual(87,
+        begin
+            ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+            call(Pid, update, [{progress, 50}]),
+            call(Pid, update_frequency, 500),
+            call(Pid, update, [{progress, 66}]),
+            call(Pid, update, [{progress, 77}]),
+            call(Pid, update_frequency, 0),
+            call(Pid, update, [{progress, 87}]),
+            get_task_prop(Pid, progress)
+        end).
+
+should_track_multiple_tasks(_) ->
+    ?_assert(run_multiple_tasks()).
+
+should_finish_task({_, Pid}) ->
+    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
+    ?assertEqual(1, length(couch_task_status:all())),
+    ok = call(Pid, done),
+    ?_assertEqual(0, length(couch_task_status:all())).
+
+
+run_multiple_tasks() ->
+    Pid1 = spawn(fun() -> loop() end),
+    Pid2 = spawn(fun() -> loop() end),
+    Pid3 = spawn(fun() -> loop() end),
+    call(Pid1, add, [{type, replication}, {progress, 0}]),
+    call(Pid2, add, [{type, compaction}, {progress, 0}]),
+    call(Pid3, add, [{type, indexer}, {progress, 0}]),
+
+    ?assertEqual(3, length(couch_task_status:all())),
+    ?assertEqual(replication, get_task_prop(Pid1, type)),
+    ?assertEqual(compaction, get_task_prop(Pid2, type)),
+    ?assertEqual(indexer, get_task_prop(Pid3, type)),
+
+    call(Pid2, update, [{progress, 33}]),
+    call(Pid3, update, [{progress, 42}]),
+    call(Pid1, update, [{progress, 11}]),
+    ?assertEqual(42, get_task_prop(Pid3, progress)),
+    call(Pid1, update, [{progress, 72}]),
+    ?assertEqual(72, get_task_prop(Pid1, progress)),
+    ?assertEqual(33, get_task_prop(Pid2, progress)),
+
+    call(Pid1, done),
+    ?assertEqual(2, length(couch_task_status:all())),
+    call(Pid3, done),
+    ?assertEqual(1, length(couch_task_status:all())),
+    call(Pid2, done),
+    ?assertEqual(0, length(couch_task_status:all())),
+
+    true.
+
+
+loop() ->
+    receive
+        {add, Props, From} ->
+            Resp = couch_task_status:add_task(Props),
+            From ! {ok, self(), Resp},
+            loop();
+        {update, Props, From} ->
+            Resp = couch_task_status:update(Props),
+            From ! {ok, self(), Resp},
+            loop();
+        {update_frequency, Msecs, From} ->
+            Resp = couch_task_status:set_update_frequency(Msecs),
+            From ! {ok, self(), Resp},
+            loop();
+        {done, From} ->
+            From ! {ok, self(), ok}
+    end.
+
+call(Pid, Command) ->
+    Pid ! {Command, self()},
+    wait(Pid).
+
+call(Pid, Command, Arg) ->
+    Pid ! {Command, Arg, self()},
+    wait(Pid).
+
+wait(Pid) ->
+    receive
+        {ok, Pid, Msg} ->
+            Msg
+    after ?TIMEOUT ->
+        throw(timeout_error)
+    end.
+
+get_task_prop(Pid, Prop) ->
+    From = list_to_binary(pid_to_list(Pid)),
+    Element = lists:foldl(
+        fun(PropList, Acc) ->
+            case couch_util:get_value(pid, PropList) of
+                From ->
+                    [PropList | Acc];
+                _ ->
+                    Acc
+            end
+        end,
+        [], couch_task_status:all()
+    ),
+    case couch_util:get_value(Prop, hd(Element), nil) of
+        nil ->
+            erlang:error({assertion_failed,
+                         [{module, ?MODULE},
+                          {line, ?LINE},
+                          {reason, "Could not get property '"
+                                   ++ couch_util:to_list(Prop)
+                                   ++ "' for task "
+                                   ++ pid_to_list(Pid)}]});
+        Value ->
+            Value
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_util_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_util_tests.erl b/test/couch_util_tests.erl
new file mode 100644
index 0000000..8e24e72
--- /dev/null
+++ b/test/couch_util_tests.erl
@@ -0,0 +1,136 @@
+% 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_util_tests).
+
+-include("couch_eunit.hrl").
+
+
+setup() ->
+    %% We cannot start driver from here since it becomes bounded to eunit
+    %% master process and the next couch_server_sup:start_link call will
+    %% fail because server couldn't load driver since it already is.
+    %%
+    %% On other hand, we cannot unload driver here due to
+    %% {error, not_loaded_by_this_process} while it is. Any ideas is welcome.
+    %%
+    couch_server_sup:start_link(?CONFIG_CHAIN),
+    %% couch_config:start_link(?CONFIG_CHAIN),
+    %% {ok, _} = couch_drv:start_link(),
+    ok.
+
+teardown(_) ->
+    couch_server_sup:stop(),
+    %% couch_config:stop(),
+    %% erl_ddll:unload_driver(couch_icu_driver),
+    ok.
+
+
+collation_test_() ->
+    {
+        "Collation tests",
+        [
+            {
+                setup,
+                fun setup/0, fun teardown/1,
+                [
+                    should_collate_ascii(),
+                    should_collate_non_ascii()
+                ]
+            }
+        ]
+    }.
+
+should_collate_ascii() ->
+    ?_assertEqual(1, couch_util:collate(<<"foo">>, <<"bar">>)).
+
+should_collate_non_ascii() ->
+    ?_assertEqual(-1, couch_util:collate(<<"A">>, <<"aa">>)).
+
+to_existed_atom_test() ->
+    ?assert(couch_util:to_existing_atom(true)),
+    ?assertMatch(foo, couch_util:to_existing_atom(<<"foo">>)),
+    ?assertMatch(foobarbaz, couch_util:to_existing_atom("foobarbaz")).
+
+implode_test() ->
+    ?assertEqual([1, 38, 2, 38, 3], couch_util:implode([1, 2, 3], "&")).
+
+trim_test() ->
+    lists:map(fun(S) -> ?assertEqual("foo", couch_util:trim(S)) end,
+              [" foo", "foo ", "\tfoo", " foo ", "foo\t", "foo\n", "\nfoo"]).
+
+abs_pathname_test() ->
+    {ok, Cwd} = file:get_cwd(),
+    ?assertEqual(Cwd ++ "/foo", couch_util:abs_pathname("./foo")).
+
+flush_test() ->
+    ?assertNot(couch_util:should_flush()),
+    AcquireMem = fun() ->
+        _IntsToAGazillion = lists:seq(1, 200000),
+        _LotsOfData = lists:map(fun(_) -> <<"foobar">> end,
+                                lists:seq(1, 500000)),
+        _BigBin = list_to_binary(_LotsOfData),
+
+        %% Allocation 200K tuples puts us above the memory threshold
+        %% Originally, there should be:
+        %%      ?assertNot(should_flush())
+        %% however, unlike for etap test, GC collects all allocated bits
+        %% making this conditions fail. So we have to invert the condition
+        %% since GC works, cleans the memory and everything is fine.
+        ?assertNot(couch_util:should_flush())
+    end,
+    AcquireMem(),
+
+    %% Checking to flush invokes GC
+    ?assertNot(couch_util:should_flush()).
+
+verify_test() ->
+    ?assert(couch_util:verify("It4Vooya", "It4Vooya")),
+    ?assertNot(couch_util:verify("It4VooyaX", "It4Vooya")),
+    ?assert(couch_util:verify(<<"ahBase3r">>, <<"ahBase3r">>)),
+    ?assertNot(couch_util:verify(<<"ahBase3rX">>, <<"ahBase3r">>)),
+    ?assertNot(couch_util:verify(nil, <<"ahBase3r">>)).
+
+find_in_binary_test_() ->
+    Cases = [
+        {<<"foo">>, <<"foobar">>, {exact, 0}},
+        {<<"foo">>, <<"foofoo">>, {exact, 0}},
+        {<<"foo">>, <<"barfoo">>, {exact, 3}},
+        {<<"foo">>, <<"barfo">>, {partial, 3}},
+        {<<"f">>, <<"fobarfff">>, {exact, 0}},
+        {<<"f">>, <<"obarfff">>, {exact, 4}},
+        {<<"f">>, <<"obarggf">>, {exact, 6}},
+        {<<"f">>, <<"f">>, {exact, 0}},
+        {<<"f">>, <<"g">>, not_found},
+        {<<"foo">>, <<"f">>, {partial, 0}},
+        {<<"foo">>, <<"g">>, not_found},
+        {<<"foo">>, <<"">>, not_found},
+        {<<"fofo">>, <<"foofo">>, {partial, 3}},
+        {<<"foo">>, <<"gfobarfo">>, {partial, 6}},
+        {<<"foo">>, <<"gfobarf">>, {partial, 6}},
+        {<<"foo">>, <<"gfobar">>, not_found},
+        {<<"fog">>, <<"gbarfogquiz">>, {exact, 4}},
+        {<<"ggg">>, <<"ggg">>, {exact, 0}},
+        {<<"ggg">>, <<"ggggg">>, {exact, 0}},
+        {<<"ggg">>, <<"bggg">>, {exact, 1}},
+        {<<"ggg">>, <<"bbgg">>, {partial, 2}},
+        {<<"ggg">>, <<"bbbg">>, {partial, 3}},
+        {<<"ggg">>, <<"bgbggbggg">>, {exact, 6}},
+        {<<"ggg">>, <<"bgbggb">>, not_found}
+    ],
+    lists:map(
+        fun({Needle, Haystack, Result}) ->
+            Msg = lists:flatten(io_lib:format("Looking for ~s in ~s",
+                                              [Needle, Haystack])),
+            {Msg, ?_assertMatch(Result,
+                                couch_util:find_in_binary(Needle, Haystack))}
+        end, Cases).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_uuids_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_uuids_tests.erl b/test/couch_uuids_tests.erl
new file mode 100644
index 0000000..ea1d034
--- /dev/null
+++ b/test/couch_uuids_tests.erl
@@ -0,0 +1,161 @@
+% 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_uuids_tests).
+
+-include("couch_eunit.hrl").
+
+-define(TIMEOUT_S, 20).
+
+
+setup() ->
+    {ok, Pid} = couch_config:start_link(?CONFIG_CHAIN),
+    erlang:monitor(process, Pid),
+    couch_uuids:start(),
+    Pid.
+
+setup(Opts) ->
+    Pid = setup(),
+    lists:foreach(
+        fun({Option, Value}) ->
+            couch_config:set("uuids", Option, Value, false)
+        end, Opts),
+    Pid.
+
+teardown(Pid) ->
+    couch_uuids:stop(),
+    couch_config:stop(),
+    receive
+        {'DOWN', _, _, Pid, _} -> ok
+    after
+        1000 -> throw({timeout_error, config_stop})
+    end.
+
+teardown(_, Pid) ->
+    teardown(Pid).
+
+
+default_test_() ->
+    {
+        "Default UUID algorithm",
+        {
+            setup,
+            fun setup/0, fun teardown/1,
+            fun should_be_unique/1
+        }
+    }.
+
+sequential_test_() ->
+    Opts = [{"algorithm", "sequential"}],
+    Cases = [
+        fun should_be_unique/2,
+        fun should_increment_monotonically/2,
+        fun should_rollover/2
+    ],
+    {
+        "UUID algorithm: sequential",
+        {
+            foreachx,
+            fun setup/1, fun teardown/2,
+            [{Opts, Fun} || Fun <- Cases]
+        }
+    }.
+
+utc_test_() ->
+    Opts = [{"algorithm", "utc_random"}],
+    Cases = [
+        fun should_be_unique/2,
+        fun should_increment_monotonically/2
+    ],
+    {
+        "UUID algorithm: utc_random",
+        {
+            foreachx,
+            fun setup/1, fun teardown/2,
+            [{Opts, Fun} || Fun <- Cases]
+        }
+    }.
+
+utc_id_suffix_test_() ->
+    Opts = [{"algorithm", "utc_id"}, {"utc_id_suffix", "bozo"}],
+    Cases = [
+        fun should_be_unique/2,
+        fun should_increment_monotonically/2,
+        fun should_preserve_suffix/2
+    ],
+    {
+        "UUID algorithm: utc_id",
+        {
+            foreachx,
+            fun setup/1, fun teardown/2,
+            [{Opts, Fun} || Fun <- Cases]
+        }
+    }.
+
+
+should_be_unique() ->
+    %% this one may really runs for too long on slow hosts
+    {timeout, ?TIMEOUT_S, ?_assert(test_unique(10000, [couch_uuids:new()]))}.
+should_be_unique(_) ->
+    should_be_unique().
+should_be_unique(_, _) ->
+    should_be_unique().
+
+should_increment_monotonically(_, _) ->
+    ?_assert(couch_uuids:new() < couch_uuids:new()).
+
+should_rollover(_, _) ->
+    ?_test(begin
+        UUID = binary_to_list(couch_uuids:new()),
+        Prefix = element(1, lists:split(26, UUID)),
+        N = gen_until_pref_change(Prefix, 0),
+        ?assert(N >= 5000 andalso N =< 11000)
+    end).
+
+should_preserve_suffix(_, _) ->
+    ?_test(begin
+        UUID = binary_to_list(couch_uuids:new()),
+        Suffix = get_suffix(UUID),
+        ?assert(test_same_suffix(10000, Suffix))
+    end).
+
+
+test_unique(0, _) ->
+    true;
+test_unique(N, UUIDs) ->
+    UUID = couch_uuids:new(),
+    ?assertNot(lists:member(UUID, UUIDs)),
+    test_unique(N - 1, [UUID| UUIDs]).
+
+get_prefix(UUID) ->
+    element(1, lists:split(26, binary_to_list(UUID))).
+
+gen_until_pref_change(_, Count) when Count > 8251 ->
+    Count;
+gen_until_pref_change(Prefix, N) ->
+    case get_prefix(couch_uuids:new()) of
+        Prefix -> gen_until_pref_change(Prefix, N + 1);
+        _ -> N
+    end.
+
+get_suffix(UUID) when is_binary(UUID) ->
+    get_suffix(binary_to_list(UUID));
+get_suffix(UUID) ->
+    element(2, lists:split(14, UUID)).
+
+test_same_suffix(0, _) ->
+    true;
+test_same_suffix(N, Suffix) ->
+    case get_suffix(couch_uuids:new()) of
+        Suffix -> test_same_suffix(N - 1, Suffix);
+        _ -> false
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couch_work_queue_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_work_queue_tests.erl b/test/couch_work_queue_tests.erl
new file mode 100644
index 0000000..8a463b5
--- /dev/null
+++ b/test/couch_work_queue_tests.erl
@@ -0,0 +1,393 @@
+% 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_work_queue_tests).
+
+-include("couch_eunit.hrl").
+
+-define(TIMEOUT, 100).
+
+
+setup(Opts) ->
+    {ok, Q} = couch_work_queue:new(Opts),
+    Producer = spawn_producer(Q),
+    Consumer = spawn_consumer(Q),
+    {Q, Producer, Consumer}.
+
+setup_max_items() ->
+    setup([{max_items, 3}]).
+
+setup_max_size() ->
+    setup([{max_size, 160}]).
+
+setup_max_items_and_size() ->
+    setup([{max_size, 160}, {max_items, 3}]).
+
+setup_multi_workers() ->
+    {Q, Producer, Consumer1} = setup([{max_size, 160},
+                                      {max_items, 3},
+                                      {multi_workers, true}]),
+    Consumer2 = spawn_consumer(Q),
+    Consumer3 = spawn_consumer(Q),
+    {Q, Producer, [Consumer1, Consumer2, Consumer3]}.
+
+teardown({Q, Producer, Consumers}) when is_list(Consumers) ->
+    % consume all to unblock and let producer/consumer stop without timeout
+    [consume(Consumer, all) || Consumer <- Consumers],
+
+    ok = close_queue(Q),
+    ok = stop(Producer, "producer"),
+    R = [stop(Consumer, "consumer") || Consumer <- Consumers],
+    R = [ok || _ <- Consumers],
+    ok;
+teardown({Q, Producer, Consumer}) ->
+    teardown({Q, Producer, [Consumer]}).
+
+
+single_consumer_test_() ->
+    {
+        "Single producer and consumer",
+        [
+            {
+                "Queue with 3 max items",
+                {
+                    foreach,
+                    fun setup_max_items/0, fun teardown/1,
+                    single_consumer_max_item_count() ++ common_cases()
+                }
+            },
+            {
+                "Queue with max size of 160 bytes",
+                {
+                    foreach,
+                    fun setup_max_size/0, fun teardown/1,
+                    single_consumer_max_size() ++ common_cases()
+                }
+            },
+            {
+                "Queue with max size of 160 bytes and 3 max items",
+                {
+                    foreach,
+                    fun setup_max_items_and_size/0, fun teardown/1,
+                    single_consumer_max_items_and_size() ++ common_cases()
+                }
+            }
+        ]
+    }.
+
+multiple_consumers_test_() ->
+    {
+        "Single producer and multiple consumers",
+        [
+            {
+                "Queue with max size of 160 bytes and 3 max items",
+                {
+                    foreach,
+                    fun setup_multi_workers/0, fun teardown/1,
+                    common_cases() ++ multiple_consumers()
+                }
+
+            }
+        ]
+    }.
+
+common_cases()->
+    [
+        fun should_block_consumer_on_dequeue_from_empty_queue/1,
+        fun should_consume_right_item/1,
+        fun should_timeout_on_close_non_empty_queue/1,
+        fun should_not_block_producer_for_non_empty_queue_after_close/1,
+        fun should_be_closed/1
+    ].
+
+single_consumer_max_item_count()->
+    [
+        fun should_have_no_items_for_new_queue/1,
+        fun should_block_producer_on_full_queue_count/1,
+        fun should_receive_first_queued_item/1,
+        fun should_consume_multiple_items/1,
+        fun should_consume_all/1
+    ].
+
+single_consumer_max_size()->
+    [
+        fun should_have_zero_size_for_new_queue/1,
+        fun should_block_producer_on_full_queue_size/1,
+        fun should_increase_queue_size_on_produce/1,
+        fun should_receive_first_queued_item/1,
+        fun should_consume_multiple_items/1,
+        fun should_consume_all/1
+    ].
+
+single_consumer_max_items_and_size() ->
+    single_consumer_max_item_count() ++ single_consumer_max_size().
+
+multiple_consumers() ->
+    [
+        fun should_have_zero_size_for_new_queue/1,
+        fun should_have_no_items_for_new_queue/1,
+        fun should_increase_queue_size_on_produce/1
+    ].
+
+
+should_have_no_items_for_new_queue({Q, _, _}) ->
+    ?_assertEqual(0, couch_work_queue:item_count(Q)).
+
+should_have_zero_size_for_new_queue({Q, _, _}) ->
+    ?_assertEqual(0, couch_work_queue:size(Q)).
+
+should_block_consumer_on_dequeue_from_empty_queue({_, _, Consumers}) when is_list(Consumers) ->
+    [consume(C, 2) || C <- Consumers],
+    Pongs = [ping(C) || C <- Consumers],
+    ?_assertEqual([timeout, timeout, timeout], Pongs);
+should_block_consumer_on_dequeue_from_empty_queue({_, _, Consumer}) ->
+    consume(Consumer, 1),
+    Pong = ping(Consumer),
+    ?_assertEqual(timeout, Pong).
+
+should_consume_right_item({Q, Producer, Consumers}) when is_list(Consumers) ->
+    [consume(C, 3) || C <- Consumers],
+
+    Item1 = produce(Producer, 10),
+    ok = ping(Producer),
+    ?assertEqual(0, couch_work_queue:item_count(Q)),
+    ?assertEqual(0, couch_work_queue:size(Q)),
+
+    Item2 = produce(Producer, 10),
+    ok = ping(Producer),
+    ?assertEqual(0, couch_work_queue:item_count(Q)),
+    ?assertEqual(0, couch_work_queue:size(Q)),
+
+    Item3 = produce(Producer, 10),
+    ok = ping(Producer),
+    ?assertEqual(0, couch_work_queue:item_count(Q)),
+    ?assertEqual(0, couch_work_queue:size(Q)),
+
+    R = [{ping(C), Item}
+         || {C, Item} <- lists:zip(Consumers, [Item1, Item2, Item3])],
+
+    ?_assertEqual([{ok, Item1}, {ok, Item2}, {ok, Item3}], R);
+should_consume_right_item({_, Producer, Consumer}) ->
+    consume(Consumer, 1),
+    Item = produce(Producer, 10),
+    produce(Producer, 20),
+    ok = ping(Producer),
+    ok = ping(Consumer),
+    {ok, Items} = last_consumer_items(Consumer),
+    ?_assertEqual([Item], Items).
+
+should_increase_queue_size_on_produce({Q, Producer, _}) ->
+    produce(Producer, 50),
+    ok = ping(Producer),
+    Count1 = couch_work_queue:item_count(Q),
+    Size1 = couch_work_queue:size(Q),
+
+    produce(Producer, 10),
+    Count2 = couch_work_queue:item_count(Q),
+    Size2 = couch_work_queue:size(Q),
+
+    ?_assertEqual([{Count1, Size1}, {Count2, Size2}], [{1, 50}, {2, 60}]).
+
+should_block_producer_on_full_queue_count({Q, Producer, _}) ->
+    produce(Producer, 10),
+    ?assertEqual(1, couch_work_queue:item_count(Q)),
+    ok = ping(Producer),
+
+    produce(Producer, 15),
+    ?assertEqual(2, couch_work_queue:item_count(Q)),
+    ok = ping(Producer),
+
+    produce(Producer, 20),
+    ?assertEqual(3, couch_work_queue:item_count(Q)),
+    Pong = ping(Producer),
+
+    ?_assertEqual(timeout, Pong).
+
+should_block_producer_on_full_queue_size({Q, Producer, _}) ->
+    produce(Producer, 100),
+    ok = ping(Producer),
+    ?assertEqual(1, couch_work_queue:item_count(Q)),
+    ?assertEqual(100, couch_work_queue:size(Q)),
+
+    produce(Producer, 110),
+    Pong = ping(Producer),
+    ?assertEqual(2, couch_work_queue:item_count(Q)),
+    ?assertEqual(210, couch_work_queue:size(Q)),
+
+    ?_assertEqual(timeout, Pong).
+
+should_consume_multiple_items({_, Producer, Consumer}) ->
+    Item1 = produce(Producer, 10),
+    ok = ping(Producer),
+
+    Item2 = produce(Producer, 15),
+    ok = ping(Producer),
+
+    consume(Consumer, 2),
+
+    {ok, Items} = last_consumer_items(Consumer),
+    ?_assertEqual([Item1, Item2], Items).
+
+should_receive_first_queued_item({Q, Producer, Consumer}) ->
+    consume(Consumer, 100),
+    timeout = ping(Consumer),
+
+    Item = produce(Producer, 11),
+    ok = ping(Producer),
+
+    ok = ping(Consumer),
+    ?assertEqual(0, couch_work_queue:item_count(Q)),
+
+    {ok, Items} = last_consumer_items(Consumer),
+    ?_assertEqual([Item], Items).
+
+should_consume_all({_, Producer, Consumer}) ->
+    Item1 = produce(Producer, 10),
+    Item2 = produce(Producer, 15),
+    Item3 = produce(Producer, 20),
+
+    consume(Consumer, all),
+
+    {ok, Items} = last_consumer_items(Consumer),
+    ?_assertEqual([Item1, Item2, Item3], Items).
+
+should_timeout_on_close_non_empty_queue({Q, Producer, _}) ->
+    produce(Producer, 1),
+    Status = close_queue(Q),
+
+    ?_assertEqual(timeout, Status).
+
+should_not_block_producer_for_non_empty_queue_after_close({Q, Producer, _}) ->
+    produce(Producer, 1),
+    close_queue(Q),
+    Pong = ping(Producer),
+    Size = couch_work_queue:size(Q),
+    Count = couch_work_queue:item_count(Q),
+
+    ?_assertEqual({ok, 1, 1}, {Pong, Size, Count}).
+
+should_be_closed({Q, _, Consumers}) when is_list(Consumers) ->
+    ok = close_queue(Q),
+
+    [consume(C, 1) || C <- Consumers],
+
+    LastConsumerItems = [last_consumer_items(C) || C <- Consumers],
+    ItemsCount = couch_work_queue:item_count(Q),
+    Size = couch_work_queue:size(Q),
+
+    ?_assertEqual({[closed, closed, closed], closed, closed},
+                  {LastConsumerItems, ItemsCount, Size});
+should_be_closed({Q, _, Consumer}) ->
+    ok = close_queue(Q),
+
+    consume(Consumer, 1),
+
+    LastConsumerItems = last_consumer_items(Consumer),
+    ItemsCount = couch_work_queue:item_count(Q),
+    Size = couch_work_queue:size(Q),
+
+    ?_assertEqual({closed, closed, closed},
+                  {LastConsumerItems, ItemsCount, Size}).
+
+
+close_queue(Q) ->
+    ok = couch_work_queue:close(Q),
+    MonRef = erlang:monitor(process, Q),
+    receive
+        {'DOWN', MonRef, process, Q, _Reason} -> ok
+    after ?TIMEOUT ->
+        erlang:demonitor(MonRef),
+        timeout
+    end.
+
+spawn_consumer(Q) ->
+    Parent = self(),
+    spawn(fun() -> consumer_loop(Parent, Q, nil) end).
+
+consumer_loop(Parent, Q, PrevItem) ->
+    receive
+        {stop, Ref} ->
+            Parent ! {ok, Ref};
+        {ping, Ref} ->
+            Parent ! {pong, Ref},
+            consumer_loop(Parent, Q, PrevItem);
+        {last_item, Ref} ->
+            Parent ! {item, Ref, PrevItem},
+            consumer_loop(Parent, Q, PrevItem);
+        {consume, N} ->
+            Result = couch_work_queue:dequeue(Q, N),
+            consumer_loop(Parent, Q, Result)
+    end.
+
+spawn_producer(Q) ->
+    Parent = self(),
+    spawn(fun() -> producer_loop(Parent, Q) end).
+
+producer_loop(Parent, Q) ->
+    receive
+        {stop, Ref} ->
+            Parent ! {ok, Ref};
+        {ping, Ref} ->
+            Parent ! {pong, Ref},
+            producer_loop(Parent, Q);
+        {produce, Ref, Size} ->
+            Item = crypto:rand_bytes(Size),
+            Parent ! {item, Ref, Item},
+            ok = couch_work_queue:queue(Q, Item),
+            producer_loop(Parent, Q)
+    end.
+
+consume(Consumer, N) ->
+    Consumer ! {consume, N}.
+
+last_consumer_items(Consumer) ->
+    Ref = make_ref(),
+    Consumer ! {last_item, Ref},
+    receive
+        {item, Ref, Items} ->
+            Items
+    after ?TIMEOUT ->
+        timeout
+    end.
+
+produce(Producer, Size) ->
+    Ref = make_ref(),
+    Producer ! {produce, Ref, Size},
+    receive
+        {item, Ref, Item} ->
+            Item
+    after ?TIMEOUT ->
+        erlang:error({assertion_failed,
+                      [{module, ?MODULE},
+                       {line, ?LINE},
+                       {reason, "Timeout asking producer to produce an item"}]})
+    end.
+
+ping(Pid) ->
+    Ref = make_ref(),
+    Pid ! {ping, Ref},
+    receive
+        {pong, Ref} ->
+            ok
+    after ?TIMEOUT ->
+        timeout
+    end.
+
+stop(Pid, Name) ->
+    Ref = make_ref(),
+    Pid ! {stop, Ref},
+    receive
+        {ok, Ref} -> ok
+    after ?TIMEOUT ->
+        ?debugMsg("Timeout stopping " ++ Name),
+        timeout
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0ce84d8e/test/couchdb/couch_auth_cache_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb/couch_auth_cache_tests.erl b/test/couchdb/couch_auth_cache_tests.erl
deleted file mode 100644
index 3b2321c..0000000
--- a/test/couchdb/couch_auth_cache_tests.erl
+++ /dev/null
@@ -1,238 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_auth_cache_tests).
-
--include("couch_eunit.hrl").
--include_lib("couchdb/couch_db.hrl").
-
--define(ADMIN_USER, {user_ctx, #user_ctx{roles=[<<"_admin">>]}}).
--define(SALT, <<"SALT">>).
--define(TIMEOUT, 1000).
-
-
-start() ->
-    {ok, Pid} = couch_server_sup:start_link(?CONFIG_CHAIN),
-    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(),
-    couch_config:set("couch_httpd_auth", "authentication_db",
-                     ?b2l(DbName), false),
-    DbName.
-
-teardown(DbName) ->
-    ok = couch_server:delete(DbName, [?ADMIN_USER]),
-    ok.
-
-
-couch_auth_cache_test_() ->
-    {
-        "CouchDB auth cache tests",
-        {
-            setup,
-            fun start/0, fun stop/1,
-            {
-                foreach,
-                fun setup/0, fun teardown/1,
-                [
-                    fun should_get_nil_on_missed_cache/1,
-                    fun should_get_right_password_hash/1,
-                    fun should_ensure_doc_hash_equals_cached_one/1,
-                    fun should_update_password/1,
-                    fun should_cleanup_cache_after_userdoc_deletion/1,
-                    fun should_restore_cache_after_userdoc_recreation/1,
-                    fun should_drop_cache_on_auth_db_change/1,
-                    fun should_restore_cache_on_auth_db_change/1,
-                    fun should_recover_cache_after_shutdown/1
-                ]
-            }
-        }
-    }.
-
-
-should_get_nil_on_missed_cache(_) ->
-    ?_assertEqual(nil, couch_auth_cache:get_user_creds("joe")).
-
-should_get_right_password_hash(DbName) ->
-    ?_test(begin
-        PasswordHash = hash_password("pass1"),
-        {ok, _} = update_user_doc(DbName, "joe", "pass1"),
-        Creds = couch_auth_cache:get_user_creds("joe"),
-        ?assertEqual(PasswordHash,
-                      couch_util:get_value(<<"password_sha">>, Creds))
-    end).
-
-should_ensure_doc_hash_equals_cached_one(DbName) ->
-    ?_test(begin
-        {ok, _} = update_user_doc(DbName, "joe", "pass1"),
-        Creds = couch_auth_cache:get_user_creds("joe"),
-
-        CachedHash = couch_util:get_value(<<"password_sha">>, Creds),
-        StoredHash = get_user_doc_password_sha(DbName, "joe"),
-        ?assertEqual(StoredHash, CachedHash)
-    end).
-
-should_update_password(DbName) ->
-    ?_test(begin
-        PasswordHash = hash_password("pass2"),
-        {ok, Rev} = update_user_doc(DbName, "joe", "pass1"),
-        {ok, _} = update_user_doc(DbName, "joe", "pass2", Rev),
-        Creds = couch_auth_cache:get_user_creds("joe"),
-        ?assertEqual(PasswordHash,
-                      couch_util:get_value(<<"password_sha">>, Creds))
-    end).
-
-should_cleanup_cache_after_userdoc_deletion(DbName) ->
-    ?_test(begin
-        {ok, _} = update_user_doc(DbName, "joe", "pass1"),
-        delete_user_doc(DbName, "joe"),
-        ?assertEqual(nil, couch_auth_cache:get_user_creds("joe"))
-    end).
-
-should_restore_cache_after_userdoc_recreation(DbName) ->
-    ?_test(begin
-        PasswordHash = hash_password("pass5"),
-        {ok, _} = update_user_doc(DbName, "joe", "pass1"),
-        delete_user_doc(DbName, "joe"),
-        ?assertEqual(nil, couch_auth_cache:get_user_creds("joe")),
-
-        {ok, _} = update_user_doc(DbName, "joe", "pass5"),
-        Creds = couch_auth_cache:get_user_creds("joe"),
-
-        ?assertEqual(PasswordHash,
-                      couch_util:get_value(<<"password_sha">>, Creds))
-    end).
-
-should_drop_cache_on_auth_db_change(DbName) ->
-    ?_test(begin
-        {ok, _} = update_user_doc(DbName, "joe", "pass1"),
-        full_commit(DbName),
-        couch_config:set("couch_httpd_auth", "authentication_db",
-                         ?b2l(?tempdb()), false),
-        ?assertEqual(nil, couch_auth_cache:get_user_creds("joe"))
-    end).
-
-should_restore_cache_on_auth_db_change(DbName) ->
-    ?_test(begin
-        PasswordHash = hash_password("pass1"),
-        {ok, _} = update_user_doc(DbName, "joe", "pass1"),
-        Creds = couch_auth_cache:get_user_creds("joe"),
-        full_commit(DbName),
-
-        DbName1 = ?tempdb(),
-        couch_config:set("couch_httpd_auth", "authentication_db",
-                         ?b2l(DbName1), false),
-
-        {ok, _} = update_user_doc(DbName1, "joe", "pass5"),
-        full_commit(DbName1),
-
-        couch_config:set("couch_httpd_auth", "authentication_db",
-                         ?b2l(DbName), false),
-
-        Creds = couch_auth_cache:get_user_creds("joe"),
-        ?assertEqual(PasswordHash,
-                      couch_util:get_value(<<"password_sha">>, Creds))
-    end).
-
-should_recover_cache_after_shutdown(DbName) ->
-    ?_test(begin
-        PasswordHash = hash_password("pass2"),
-        {ok, Rev0} = update_user_doc(DbName, "joe", "pass1"),
-        {ok, Rev1} = update_user_doc(DbName, "joe", "pass2", Rev0),
-        full_commit(DbName),
-        shutdown_db(DbName),
-        {ok, Rev1} = get_doc_rev(DbName, "joe"),
-        ?assertEqual(PasswordHash, get_user_doc_password_sha(DbName, "joe"))
-    end).
-
-
-update_user_doc(DbName, UserName, Password) ->
-    update_user_doc(DbName, UserName, Password, nil).
-
-update_user_doc(DbName, UserName, Password, Rev) ->
-    User = iolist_to_binary(UserName),
-    Doc = couch_doc:from_json_obj({[
-        {<<"_id">>, <<"org.couchdb.user:", User/binary>>},
-        {<<"name">>, User},
-        {<<"type">>, <<"user">>},
-        {<<"salt">>, ?SALT},
-        {<<"password_sha">>, hash_password(Password)},
-        {<<"roles">>, []}
-    ] ++ case Rev of
-            nil -> [];
-            _ ->   [{<<"_rev">>, Rev}]
-         end
-    }),
-    {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_USER]),
-    {ok, NewRev} = couch_db:update_doc(AuthDb, Doc, []),
-    ok = couch_db:close(AuthDb),
-    {ok, couch_doc:rev_to_str(NewRev)}.
-
-hash_password(Password) ->
-    ?l2b(couch_util:to_hex(crypto:sha(iolist_to_binary([Password, ?SALT])))).
-
-shutdown_db(DbName) ->
-    {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_USER]),
-    ok = couch_db:close(AuthDb),
-    couch_util:shutdown_sync(AuthDb#db.main_pid),
-    ok = timer:sleep(1000).
-
-get_doc_rev(DbName, UserName) ->
-    DocId = iolist_to_binary([<<"org.couchdb.user:">>, UserName]),
-    {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_USER]),
-    UpdateRev =
-    case couch_db:open_doc(AuthDb, DocId, []) of
-    {ok, Doc} ->
-        {Props} = couch_doc:to_json_obj(Doc, []),
-        couch_util:get_value(<<"_rev">>, Props);
-    {not_found, missing} ->
-        nil
-    end,
-    ok = couch_db:close(AuthDb),
-    {ok, UpdateRev}.
-
-get_user_doc_password_sha(DbName, UserName) ->
-    DocId = iolist_to_binary([<<"org.couchdb.user:">>, UserName]),
-    {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_USER]),
-    {ok, Doc} = couch_db:open_doc(AuthDb, DocId, []),
-    ok = couch_db:close(AuthDb),
-    {Props} = couch_doc:to_json_obj(Doc, []),
-    couch_util:get_value(<<"password_sha">>, Props).
-
-delete_user_doc(DbName, UserName) ->
-    DocId = iolist_to_binary([<<"org.couchdb.user:">>, UserName]),
-    {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_USER]),
-    {ok, Doc} = couch_db:open_doc(AuthDb, DocId, []),
-    {Props} = couch_doc:to_json_obj(Doc, []),
-    DeletedDoc = couch_doc:from_json_obj({[
-        {<<"_id">>, DocId},
-        {<<"_rev">>, couch_util:get_value(<<"_rev">>, Props)},
-        {<<"_deleted">>, true}
-    ]}),
-    {ok, _} = couch_db:update_doc(AuthDb, DeletedDoc, []),
-    ok = couch_db:close(AuthDb).
-
-full_commit(DbName) ->
-    {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_USER]),
-    {ok, _} = couch_db:ensure_full_commit(AuthDb),
-    ok = couch_db:close(AuthDb).