1#!/usr/bin/env escript
2%% -*- erlang -*-
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% Test shutdown of development view groups - old couchdb view groups where
18% the database that has the design document is different from the database
19% that has the documents to index (design documents live in a "master"
20% database while documents live in "vbucket" databases).
21
22data_db_name() -> <<"couch_test_dev_view_group_shutdown/0">>.
23master_db_name() -> <<"couch_test_dev_view_group_shutdown/master">>.
24ddoc_name() -> <<"dev_foo">>.
25
26-record(user_ctx, {
27    name = null,
28    roles = [],
29    handler
30}).
31
32
33main(_) ->
34    test_util:init_code_path(),
35
36    etap:plan(7),
37    case (catch test()) of
38        ok ->
39            etap:end_tests();
40        Other ->
41            io:format(standard_error, "Test died abnormally: ~p", [Other]),
42            etap:bail(Other)
43    end,
44    ok.
45
46
47test() ->
48    couch_server_sup:start_link(test_util:config_files()),
49
50    create_db(data_db_name()),
51    create_db(master_db_name()),
52    create_docs(),
53    create_design_doc(),
54
55    ViewResults1 = query_view(),
56    etap:is(ViewResults1,
57            [{<<"doc1">>, 1}, {<<"doc2">>, 2}, {<<"doc3">>, 3}],
58            "Correct map view query results"),
59
60    GroupPid1 = get_group_pid(),
61    MonRef1 = erlang:monitor(process, GroupPid1),
62
63    GroupFileName1 = get_group_index_file_name(),
64    etap:is(lists:member(GroupFileName1, list_index_files()),
65            true,
66            "Found view group index file"),
67
68    etap:diag("Updating design document in master database"),
69    update_design_doc(),
70
71    receive
72    {'DOWN', MonRef1, process, GroupPid1, normal} ->
73         etap:diag("View group shutdown after ddoc update");
74    {'DOWN', MonRef1, process, GroupPid1, _Reason} ->
75         etap:bail("View group shutdown after ddoc update with unexpected reason")
76    after 10000 ->
77         etap:bail("Timeout waiting for view group shutdown")
78    end,
79
80    ViewResults2 = query_view(),
81    etap:is(ViewResults2,
82            [{<<"doc1">>, 3}, {<<"doc2">>, 6}, {<<"doc3">>, 9}],
83            "Correct map view query results after ddoc update"),
84
85    cleanup_index_files(),
86
87    GroupFileName2 = get_group_index_file_name(),
88    etap:is(lists:member(GroupFileName2, list_index_files()),
89            true,
90            "Found new view group index file"),
91    etap:is(lists:member(GroupFileName1, list_index_files()),
92            false,
93            "Old view group index file not found after cleanup"),
94
95    GroupPid2 = get_group_pid(),
96    MonRef2 = erlang:monitor(process, GroupPid2),
97
98    etap:diag("Deleting design document from master database"),
99    delete_design_doc(),
100
101    receive
102    {'DOWN', MonRef2, process, GroupPid2, normal} ->
103         etap:diag("View group shutdown after ddoc deleted");
104    {'DOWN', MonRef2, process, GroupPid2, _Reason2} ->
105         etap:bail("View group shutdown after ddoc deleted with unexpected reason")
106    after 10000 ->
107         etap:bail("Timeout waiting for view group shutdown")
108    end,
109
110    etap:diag("Creating design document again"),
111    create_design_doc(),
112
113    ViewResults3 = query_view(),
114    etap:is(ViewResults3,
115            [{<<"doc1">>, 1}, {<<"doc2">>, 2}, {<<"doc3">>, 3}],
116            "Correct map view query results after ddoc recreated"),
117
118    GroupPid3 = get_group_pid(),
119    MonRef3 = erlang:monitor(process, GroupPid3),
120
121    etap:diag("Deleting data database"),
122    delete_db(data_db_name()),
123
124    receive
125    {'DOWN', MonRef3, process, GroupPid3, shutdown} ->
126         etap:diag("View group shutdown after data database deleted");
127    {'DOWN', MonRef3, process, GroupPid3, _Reason3} ->
128         etap:bail("View group shutdown after data database  deleted with unexpected reason")
129    after 10000 ->
130         etap:bail("Timeout waiting for view group shutdown")
131    end,
132
133    etap:is(list_index_files(),
134            [],
135            "No index files after data database deleted"),
136
137    delete_db(master_db_name()),
138    couch_server_sup:stop(),
139    ok.
140
141
142create_db(DbName) ->
143    delete_db(DbName),
144    {ok, Db} = couch_db:create(
145        DbName, [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]),
146    ok = couch_db:close(Db).
147
148
149delete_db(DbName) ->
150    couch_server:delete(DbName, [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]).
151
152
153create_docs() ->
154    {ok, Db} = couch_db:open_int(
155        data_db_name(), [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]),
156    Doc1 = couch_doc:from_json_obj({[
157        {<<"meta">>, {[
158            {<<"id">>, <<"doc1">>}
159        ]}},
160        {<<"json">>, {[
161            {<<"value">>, 1}
162        ]}}
163    ]}),
164    Doc2 = couch_doc:from_json_obj({[
165        {<<"meta">>, {[
166            {<<"id">>, <<"doc2">>}
167        ]}},
168        {<<"json">>, {[
169            {<<"value">>, 2}
170        ]}}
171    ]}),
172    Doc3 = couch_doc:from_json_obj({[
173        {<<"meta">>, {[
174            {<<"id">>, <<"doc3">>}
175        ]}},
176        {<<"json">>, {[
177            {<<"value">>, 3}
178        ]}}
179    ]}),
180    ok = couch_db:update_docs(Db, [Doc1, Doc2, Doc3], [sort_docs]),
181    {ok, _} = couch_db:ensure_full_commit(Db),
182    ok = couch_db:close(Db).
183
184
185create_design_doc() ->
186    DDoc = couch_doc:from_json_obj({[
187        {<<"meta">>, {[
188            {<<"id">>, <<"_design/", (ddoc_name())/binary>>}
189        ]}},
190        {<<"json">>, {[
191            {<<"views">>, {[
192                {<<"bar">>, {[
193                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>}
194                ]}}
195            ]}}
196        ]}}
197    ]}),
198    save_ddoc(DDoc).
199
200
201update_design_doc() ->
202    DDoc = couch_doc:from_json_obj({[
203        {<<"meta">>, {[
204            {<<"id">>, <<"_design/", (ddoc_name())/binary>>}
205        ]}},
206        {<<"json">>, {[
207            {<<"views">>, {[
208                {<<"bar">>, {[
209                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value * 3); }">>}
210                ]}}
211            ]}}
212        ]}}
213    ]}),
214    save_ddoc(DDoc).
215
216
217delete_design_doc() ->
218    DDoc = couch_doc:from_json_obj({[
219        {<<"meta">>, {[
220            {<<"id">>, <<"_design/", (ddoc_name())/binary>>},
221            {<<"deleted">>, true}
222        ]}}
223    ]}),
224    save_ddoc(DDoc).
225
226
227save_ddoc(DDoc) ->
228    {ok, Db} = couch_db:open_int(
229        master_db_name(), [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]),
230    ok = couch_db:update_doc(Db, DDoc, []),
231    {ok, _} = couch_db:ensure_full_commit(Db),
232    ok = couch_db:close(Db).
233
234
235query_view() ->
236    {ok, DataDb} = couch_db:open_int(data_db_name(), []),
237    {ok, MasterDb} = couch_db:open_int(master_db_name(), []),
238    DDocId = <<"_design/", (ddoc_name())/binary>>,
239    {ok, View, _Group} = couch_view:get_map_view(
240        DataDb, {MasterDb, DDocId}, <<"bar">>, false),
241    FoldFun = fun({{Key, _DocId}, Value}, _OffsetReds, Acc) ->
242        {ok, [{Key, Value} | Acc]}
243    end,
244    {ok, _, Results} = couch_view:fold(View, FoldFun, [], []),
245    ok = couch_db:close(DataDb),
246    ok = couch_db:close(MasterDb),
247    lists:reverse(Results).
248
249
250get_group_pid() ->
251    couch_view:get_group_server({data_db_name(), master_db_name()}, ddoc_name()).
252
253
254get_group_index_file_name() ->
255    {ok, Info} = couch_view:get_group_info({data_db_name(), master_db_name()}, ddoc_name()),
256    RootDir = couch_config:get("couchdb", "view_index_dir"),
257    BaseName = binary_to_list(couch_util:get_value(signature, Info)) ++ ".view",
258    Filename = RootDir ++ "/." ++ binary_to_list(master_db_name()) ++
259        "_design" ++ "/" ++ BaseName,
260    string:to_lower(Filename).
261
262
263list_index_files() ->
264    RootDir = couch_config:get("couchdb", "view_index_dir"),
265    Files = filelib:wildcard(
266        RootDir ++ "/." ++ binary_to_list(master_db_name()) ++ "_design" ++ "/*.view"),
267    [string:to_lower(File) || File <- Files].
268
269
270cleanup_index_files() ->
271    {ok, MasterDb} = couch_db:open_int(master_db_name(), []),
272    couch_view:cleanup_index_files(MasterDb),
273    ok = couch_db:close(MasterDb).
274