1#!/usr/bin/env escript
2%% -*- Mode: Erlang; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3%%! -smp enable
4
5% Licensed under the Apache License, Version 2.0 (the "License"); you may not
6% use this file except in compliance with the License. You may obtain a copy of
7% the License at
8%
9%   http://www.apache.org/licenses/LICENSE-2.0
10%
11% Unless required by applicable law or agreed to in writing, software
12% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14% License for the specific language governing permissions and limitations under
15% the License.
16
17-include_lib("couch_set_view/include/couch_set_view.hrl").
18
19-define(b2l(B), binary_to_list(B)).
20
21test_set_name() -> <<"couch_test_set_index_compaction">>.
22num_set_partitions() -> 8.
23ddoc_id() -> <<"_design/test">>.
24num_docs() -> 8000.
25
26
27main(_) ->
28    test_util:init_code_path(),
29
30    etap:plan(25),
31    case (catch test()) of
32        ok ->
33            etap:end_tests();
34        Other ->
35            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
36            etap:bail(Other)
37    end,
38    ok.
39
40
41test() ->
42    couch_set_view_test_util:start_server(test_set_name()),
43
44    create_set(lists:seq(0, num_set_partitions() - 1), []),
45    ValueGenFun1 = fun(I) -> I end,
46    update_documents(0, num_docs(), ValueGenFun1),
47
48    GroupPid = couch_set_view:get_group_pid(
49        mapreduce_view, test_set_name(), ddoc_id(), prod),
50    etap:is(is_process_alive(GroupPid), true, "Group is alive"),
51
52    etap:diag("Querying view before database compactions"),
53    {ok, ViewResults} = query_view(num_docs()),
54
55    DbRefCounters = couch_set_view_test_util:get_db_ref_counters(
56        test_set_name(),
57        lists:seq(0, num_set_partitions() - 1)),
58
59    ok = couch_set_view_test_util:compact_set_dbs(
60        test_set_name(),
61        lists:seq(0, num_set_partitions() - 1),
62        true),
63
64    ok = timer:sleep(3000),
65
66    etap:is(is_process_alive(GroupPid), true,
67        "Group is alive after database compactions"),
68
69    DbRefCountersAfter = couch_set_view_test_util:get_db_ref_counters(
70        test_set_name(),
71        lists:seq(0, num_set_partitions() - 1)),
72
73    lists:foreach(
74        fun({{DbName, OldRefCounter}, {DbName, NewRefCounter}}) ->
75            etap:isnt(NewRefCounter, OldRefCounter,
76                "Database " ++ ?b2l(DbName) ++ " has a new ref counter"),
77            etap:is(is_process_alive(OldRefCounter), false,
78                "Database " ++ ?b2l(DbName) ++ " old ref counter is dead")
79        end,
80        lists:zip(DbRefCounters, DbRefCountersAfter)),
81
82    etap:diag("Querying view after database compactions"),
83    {ok, ViewResults2} = query_view(num_docs()),
84
85    etap:is(ViewResults2, ViewResults,
86        "Same view results after database compactions"),
87
88    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
89    couch_set_view_test_util:stop_server(),
90    ok.
91
92
93query_view(ExpectedRowCount) ->
94    {ok, {ViewResults}} = couch_set_view_test_util:query_view(
95        test_set_name(), ddoc_id(), <<"test">>),
96    etap:is(
97        length(couch_util:get_value(<<"rows">>, ViewResults)),
98        ExpectedRowCount,
99        "Got " ++ integer_to_list(ExpectedRowCount) ++ " view rows"),
100    SortedKeys =  couch_set_view_test_util:are_view_keys_sorted(
101        {ViewResults}, fun(A, B) -> A < B end),
102    etap:is(SortedKeys, true, "View result keys are sorted"),
103    {ok, {ViewResults}}.
104
105
106create_set(ActiveParts, PassiveParts) ->
107    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
108    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
109    couch_set_view:cleanup_index_files(mapreduce_view, test_set_name()),
110    etap:diag("Creating the set databases (# of partitions: " ++
111        integer_to_list(num_set_partitions()) ++ ")"),
112    DDoc = {[
113        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
114        {<<"json">>, {[
115            {<<"language">>, <<"javascript">>},
116            {<<"views">>, {[
117                {<<"test">>, {[
118                    {<<"map">>, <<"function(doc, meta) { emit(doc.value, meta.id); }">>}
119                ]}}
120            ]}}
121        ]}}
122    ]},
123    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
124    etap:diag("Configuring set view with partitions [0 .. 31]"
125              " as active and [32 .. 47] as passive"),
126    Params = #set_view_params{
127        max_partitions = num_set_partitions(),
128        active_partitions = ActiveParts,
129        passive_partitions = PassiveParts,
130        use_replica_index = true
131    },
132    ok = couch_set_view:define_group(
133        mapreduce_view, test_set_name(), ddoc_id(), Params).
134
135
136update_documents(StartId, Count, ValueGenFun) ->
137    etap:diag("Updating " ++ integer_to_list(Count) ++ " new documents"),
138    DocList0 = lists:map(
139        fun(I) ->
140            {I rem num_set_partitions(), {[
141                    {<<"meta">>, {[{<<"id">>, doc_id(I)}]}},
142                    {<<"json">>, {[
143                        {<<"value">>, ValueGenFun(I)}
144                    ]}}
145            ]}}
146        end,
147        lists:seq(StartId, StartId + Count - 1)),
148    DocList = [Doc || {_, Doc} <- lists:keysort(1, DocList0)],
149    ok = couch_set_view_test_util:populate_set_sequentially(
150        test_set_name(),
151        lists:seq(0, num_set_partitions() - 1),
152        DocList).
153
154
155doc_id(I) ->
156    iolist_to_binary(io_lib:format("doc_~8..0b", [I])).
157