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% from couch_db.hrl
20-define(MIN_STR, <<>>).
21-define(MAX_STR, <<255>>).
22
23-record(view_query_args, {
24    start_key,
25    end_key,
26    start_docid = ?MIN_STR,
27    end_docid = ?MAX_STR,
28    direction = fwd,
29    inclusive_end = true,
30    limit = 10000000000,
31    skip = 0,
32    group_level = 0,
33    view_type = nil,
34    include_docs = false,
35    conflicts = false,
36    stale = false,
37    multi_get = false,
38    callback = nil,
39    list = nil,
40    run_reduce = true,
41    keys = nil,
42    view_name = nil,
43    debug = false,
44    filter = true,
45    type = main
46}).
47
48-define(etap_match(Got, Expected, Desc),
49        etap:fun_is(fun(XXXXXX) ->
50            case XXXXXX of Expected -> true; _ -> false end
51        end, Got, Desc)).
52
53test_set_name() -> <<"couch_test_set_index_errors">>.
54num_set_partitions() -> 4.
55num_docs() -> 1000.
56
57
58main(_) ->
59    test_util:init_code_path(),
60
61    etap:plan(31),
62    case (catch test()) of
63        ok ->
64            etap:end_tests();
65        Other ->
66            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
67            etap:bail(Other)
68    end,
69    ok.
70
71
72test() ->
73    couch_set_view_test_util:start_server(test_set_name()),
74
75    etap:diag("Testing map function with a runtime error"),
76    test_map_runtime_error(),
77    etap:diag("Testing map function with a runtime error for a group of a views"),
78    test_map_runtime_error_multiple_views(),
79    etap:diag("Testing map function with invalid syntax"),
80    test_map_syntax_error(),
81
82    etap:diag("Testing case where map function emits a key that is too long"),
83    test_too_long_map_key(),
84
85    etap:diag("Testing case where map function emits a value that is too long"),
86    test_too_long_map_value(),
87
88    etap:diag("Testing case where too many KV pairs are emitted for a single document"),
89    test_too_many_keys_per_doc(),
90
91    etap:diag("Testing builtin reduce _sum function with a runtime error (initial build)"),
92    test_builtin_reduce_sum_runtime_error(),
93    etap:diag("Testing builtin reduce _sum function with a runtime error (incr update)"),
94    test_builtin_reduce_sum_runtime_error2(),
95    etap:diag("Testing builtin reduce _stats function with a runtime error (initial build)"),
96    test_builtin_reduce_stats_runtime_error(),
97    etap:diag("Testing builtin reduce _stats function with a runtime error (incr update)"),
98    test_builtin_reduce_stats_runtime_error2(),
99
100    etap:diag("Testing with an invalid builtin reduce function"),
101    test_invalid_builtin_reduce_error(),
102    etap:diag("Testing reduce function with a runtime error"),
103    test_reduce_runtime_error(),
104    etap:diag("Testing reduce function with invalid syntax"),
105    test_reduce_syntax_error(),
106
107    etap:diag("Testing reduce function producing a too large reduction"),
108    test_reduce_too_large_reduction(),
109    etap:diag("Testing reduce function producing a too large re-reduction"),
110    test_reduce_too_large_rereduction(),
111
112    couch_set_view_test_util:stop_server(),
113    ok.
114
115
116test_map_runtime_error() ->
117    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
118    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
119
120    DDocId = <<"_design/test">>,
121    DDoc = {[
122        {<<"meta">>, {[{<<"id">>, DDocId}]}},
123        {<<"json">>, {[
124        {<<"language">>, <<"javascript">>},
125        {<<"views">>, {[
126            {<<"test">>, {[
127                {<<"map">>, <<"function(doc, meta) { emit(doc.value.foo.bar, 1); }">>}
128            ]}}
129        ]}}
130        ]}}
131    ]},
132    populate_set(DDoc),
133
134    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
135    GroupPid = couch_set_view:get_group_pid(
136        mapreduce_view, test_set_name(), DDocId, prod),
137    MonRef = erlang:monitor(process, GroupPid),
138
139    QueryResult = (catch query_map_view(DDocId, <<"test">>, false)),
140    etap:is(QueryResult, {ok, []}, "Map view query returned 0 rows"),
141
142    receive
143    {'DOWN', MonRef, _, _, _} ->
144        etap:bail("view group died")
145    after 5000 ->
146        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
147    end,
148    couch_util:shutdown_sync(GroupPid),
149    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
150
151
152test_map_runtime_error_multiple_views() ->
153    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
154    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
155
156    DDocId = <<"_design/test">>,
157    DDoc = {[
158        {<<"meta">>, {[{<<"id">>, DDocId}]}},
159        {<<"json">>, {[
160        {<<"views">>, {[
161            {<<"test1">>, {[
162                {<<"map">>, <<"function(doc, meta) { emit(doc.value, 1); }">>}
163            ]}},
164            {<<"test2">>, {[
165                {<<"map">>, <<"function(doc, meta) { emit(doc.value.foo.bar, 2); }">>}
166            ]}},
167            {<<"test3">>, {[
168                {<<"map">>, <<"function(doc, meta) { emit(doc.value, 3); }">>}
169            ]}}
170        ]}}
171        ]}}
172    ]},
173    populate_set(DDoc, 4),
174
175    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
176    GroupPid = couch_set_view:get_group_pid(
177        mapreduce_view, test_set_name(), DDocId, prod),
178    MonRef = erlang:monitor(process, GroupPid),
179
180    QueryResult2 = (catch query_map_view(DDocId, <<"test2">>, false)),
181    etap:is(QueryResult2, {ok, []}, "Map view test2 query returned 0 rows"),
182
183    QueryResult1 = (catch query_map_view(DDocId, <<"test1">>, false)),
184    ExpectedRows1 = [
185        {{{json, <<"1">>}, <<"doc1">>}, {json, <<"1">>}},
186        {{{json, <<"2">>}, <<"doc2">>}, {json, <<"1">>}},
187        {{{json, <<"3">>}, <<"doc3">>}, {json, <<"1">>}},
188        {{{json, <<"4">>}, <<"doc4">>}, {json, <<"1">>}}
189    ],
190    etap:is(QueryResult1, {ok, ExpectedRows1}, "Map view test1 query returned 4 rows"),
191
192    QueryResult3 = (catch query_map_view(DDocId, <<"test3">>, false)),
193    ExpectedRows3 = [
194        {{{json, <<"1">>}, <<"doc1">>}, {json, <<"3">>}},
195        {{{json, <<"2">>}, <<"doc2">>}, {json, <<"3">>}},
196        {{{json, <<"3">>}, <<"doc3">>}, {json, <<"3">>}},
197        {{{json, <<"4">>}, <<"doc4">>}, {json, <<"3">>}}
198    ],
199    etap:is(QueryResult3, {ok, ExpectedRows3}, "Map view test3 query returned 4 rows"),
200
201    receive
202    {'DOWN', MonRef, _, _, _} ->
203        etap:bail("view group died")
204    after 5000 ->
205        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
206    end,
207    couch_util:shutdown_sync(GroupPid),
208    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
209
210
211test_map_syntax_error() ->
212    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
213    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
214
215    DDocId = <<"_design/test">>,
216    DDoc = {[
217        {<<"meta">>, {[{<<"id">>, DDocId}]}},
218        {<<"json">>, {[
219        {<<"language">>, <<"javascript">>},
220        {<<"views">>, {[
221            {<<"test">>, {[
222                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 1); ">>}
223            ]}}
224        ]}}
225        ]}}
226    ]},
227    Result = try
228        couch_set_view_test_util:update_ddoc(test_set_name(), DDoc)
229    catch throw:Error ->
230        Error
231    end,
232    ?etap_match(Result, {invalid_design_doc, _}, "Design document creation got rejected"),
233    {invalid_design_doc, Reason} = Result,
234    etap:diag("Design document creation error reason: " ++ binary_to_list(Reason)),
235
236    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
237
238
239test_too_long_map_key() ->
240    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
241    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
242
243    DDocId = <<"_design/test">>,
244    DDoc = {[
245        {<<"meta">>, {[{<<"id">>, DDocId}]}},
246        {<<"json">>, {[
247        {<<"views">>, {[
248            {<<"test">>, {[
249                {<<"map">>, <<"function(doc, meta) {\n"
250                              "var key = meta.id;\n"
251                              "while (key.length < 4096) {\n"
252                              "    key = key.concat(key);\n"
253                              "}\n"
254                              "emit(key, null);\n"
255                              "}">>}
256            ]}}
257        ]}}
258        ]}}
259    ]},
260    populate_set(DDoc, 1),
261
262    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
263    GroupPid = couch_set_view:get_group_pid(
264        mapreduce_view, test_set_name(), DDocId, prod),
265    MonRef = erlang:monitor(process, GroupPid),
266
267    QueryResult = (catch query_map_view(DDocId, <<"test">>, false)),
268    ExpectedResult = {ok, []},
269    etap:is(QueryResult, ExpectedResult, "No key emitted when a key is too long"),
270
271    receive
272    {'DOWN', MonRef, _, _, _} ->
273        etap:bail("view group died")
274    after 5000 ->
275        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
276    end,
277    couch_util:shutdown_sync(GroupPid),
278    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
279
280
281test_too_long_map_value() ->
282    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
283    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
284    ok = mapreduce:set_max_kv_size_per_doc(0),
285    DDocId = <<"_design/test">>,
286    DDoc = {[
287        {<<"meta">>, {[{<<"id">>, DDocId}]}},
288        {<<"json">>, {[
289        {<<"views">>, {[
290            {<<"test">>, {[
291                {<<"map">>, <<"function(doc, meta) {\n"
292                              "var val = meta.id;\n"
293                              "while (val.length < (16 * 1024 * 1024)) {\n"
294                              "    val = val.concat(val);\n"
295                              "}\n"
296                              "emit(meta.id, val);\n"
297                              "}">>}
298            ]}}
299        ]}}
300        ]}}
301    ]},
302    populate_set(DDoc, 1),
303
304    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
305    GroupPid = couch_set_view:get_group_pid(
306        mapreduce_view, test_set_name(), DDocId, prod),
307    MonRef = erlang:monitor(process, GroupPid),
308
309    QueryResult = (catch query_map_view(DDocId, <<"test">>, false)),
310    ExpectedResult = {error, <<"value emitted for key `<ud>\"doc1\"</ud>`, document "
311                               "`<ud>doc1</ud>`, is too big (16777218 bytes)">>},
312    etap:is(QueryResult, ExpectedResult, "Got an error when a value is too long"),
313
314    receive
315    {'DOWN', MonRef, _, _, _} ->
316        etap:bail("view group died")
317    after 5000 ->
318        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
319    end,
320    ok = mapreduce:set_max_kv_size_per_doc(1 * 1024 * 1024),
321    couch_util:shutdown_sync(GroupPid),
322    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
323
324
325test_too_many_keys_per_doc() ->
326    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
327    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
328
329    DDocId = <<"_design/test">>,
330    DDoc = {[
331        {<<"meta">>, {[{<<"id">>, DDocId}]}},
332        {<<"json">>, {[
333        {<<"views">>, {[
334            {<<"test">>, {[
335                {<<"map">>, <<"function(doc, meta) {\n"
336                              "for (var i = 0; i < 70000; i++) {\n"
337                              "    emit(i, meta.id);\n"
338                              "}\n"
339                              "}">>}
340            ]}}
341        ]}}
342        ]}}
343    ]},
344    populate_set(DDoc, 1),
345
346    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
347    GroupPid = couch_set_view:get_group_pid(
348        mapreduce_view, test_set_name(), DDocId, prod),
349    MonRef = erlang:monitor(process, GroupPid),
350
351    QueryResult = (catch query_map_view(DDocId, <<"test">>, false)),
352    ExpectedResult = {error, <<"Too many (70000) keys emitted for document"
353                               " `<ud>doc1</ud>` (maximum allowed is 65535">>},
354    etap:is(QueryResult, ExpectedResult,
355            "Got an error when too many keys are emitted per document"),
356
357    receive
358    {'DOWN', MonRef, _, _, _} ->
359        etap:bail("view group died")
360    after 5000 ->
361        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
362    end,
363    couch_util:shutdown_sync(GroupPid),
364    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
365
366
367test_builtin_reduce_sum_runtime_error() ->
368    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
369    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
370
371    DDocId = <<"_design/test">>,
372    DDoc = {[
373        {<<"meta">>, {[{<<"id">>, DDocId}]}},
374        {<<"json">>, {[
375        {<<"language">>, <<"javascript">>},
376        {<<"views">>, {[
377            {<<"test">>, {[
378                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 'foobar'); }">>},
379                {<<"reduce">>, <<"_sum">>}
380            ]}}
381        ]}}
382        ]}}
383    ]},
384    populate_set(DDoc),
385
386    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
387    GroupPid = couch_set_view:get_group_pid(
388        mapreduce_view, test_set_name(), DDocId, prod),
389    MonRef = erlang:monitor(process, GroupPid),
390
391    QueryResult = try
392        query_reduce_view(DDocId, <<"test">>, false)
393    catch _:Error ->
394        Error
395    end,
396
397    etap:is(QueryResult,
398            {error, <<"Reducer: Error building index for view `test`, reason: Value is not a number (key \"doc1\")">>},
399            "Received error response"),
400
401    receive
402    {'DOWN', MonRef, _, _, _} ->
403        etap:bail("view group died")
404    after 5000 ->
405        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
406    end,
407    couch_util:shutdown_sync(GroupPid),
408    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
409
410
411test_builtin_reduce_sum_runtime_error2() ->
412    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
413    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
414
415    DDocId = <<"_design/test">>,
416    DDoc = {[
417        {<<"meta">>, {[{<<"id">>, DDocId}]}},
418        {<<"json">>, {[
419            {<<"language">>, <<"javascript">>},
420            {<<"views">>, {[
421                {<<"test">>, {[
422                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>},
423                    {<<"reduce">>, <<"_sum">>}
424                ]}}
425            ]}}
426        ]}}
427    ]},
428
429    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
430    add_documents(0, num_docs(), true),
431
432    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
433    GroupPid = couch_set_view:get_group_pid(
434        mapreduce_view, test_set_name(), DDocId, prod),
435    MonRef = erlang:monitor(process, GroupPid),
436
437    QueryResult1 = try
438        query_reduce_view(DDocId, <<"test">>, false),
439        ok
440    catch _:Error1 ->
441        Error1
442    end,
443    etap:is(QueryResult1,
444            ok, <<"Query triggering initial index build did not throw any exception">>),
445
446
447    add_documents(num_docs(), 2 * num_docs(), false),
448
449    QueryResult2 = try
450        query_reduce_view(DDocId, <<"test">>, false)
451    catch _:Error2 ->
452        Error2
453    end,
454    DocId = doc_id(num_docs()),
455    etap:is(QueryResult2,
456            {error, <<"Reducer: Error updating index for view `test`, reason: Value is not a number (key \"", DocId/binary, "\")">>},
457            "Received error response"),
458
459    receive
460    {'DOWN', MonRef, _, _, _} ->
461        etap:bail("view group died")
462    after 5000 ->
463        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
464    end,
465    couch_util:shutdown_sync(GroupPid),
466    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
467
468
469test_builtin_reduce_stats_runtime_error() ->
470    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
471    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
472
473    DDocId = <<"_design/test">>,
474    DDoc = {[
475        {<<"meta">>, {[{<<"id">>, DDocId}]}},
476        {<<"json">>, {[
477            {<<"language">>, <<"javascript">>},
478            {<<"views">>, {[
479                {<<"test">>, {[
480                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, 'foobar'); }">>},
481                    {<<"reduce">>, <<"_stats">>}
482                ]}}
483            ]}}
484        ]}}
485    ]},
486    populate_set(DDoc),
487
488    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
489    GroupPid = couch_set_view:get_group_pid(
490        mapreduce_view, test_set_name(), DDocId, prod),
491    MonRef = erlang:monitor(process, GroupPid),
492
493    QueryResult = try
494        query_reduce_view(DDocId, <<"test">>, false)
495    catch _:Error ->
496        Error
497    end,
498
499    etap:is(QueryResult,
500            {error, <<"Reducer: Error building index for view `test`, reason: Value is not a number (key \"doc1\")">>},
501            "Received error response"),
502
503    receive
504    {'DOWN', MonRef, _, _, _} ->
505        etap:bail("view group died")
506    after 5000 ->
507        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
508    end,
509    couch_util:shutdown_sync(GroupPid),
510    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
511
512
513test_builtin_reduce_stats_runtime_error2() ->
514    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
515    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
516
517    DDocId = <<"_design/test">>,
518    DDoc = {[
519        {<<"meta">>, {[{<<"id">>, DDocId}]}},
520        {<<"json">>, {[
521            {<<"language">>, <<"javascript">>},
522            {<<"views">>, {[
523                {<<"test">>, {[
524                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>},
525                    {<<"reduce">>, <<"_stats">>}
526                ]}}
527            ]}}
528        ]}}
529    ]},
530
531    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
532    add_documents(0, num_docs(), true),
533
534    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
535    GroupPid = couch_set_view:get_group_pid(
536        mapreduce_view, test_set_name(), DDocId, prod),
537    MonRef = erlang:monitor(process, GroupPid),
538
539    QueryResult1 = try
540        query_reduce_view(DDocId, <<"test">>, false),
541        ok
542    catch _:Error1 ->
543        Error1
544    end,
545    etap:is(QueryResult1,
546            ok, <<"Query triggering initial index build did not throw any exception">>),
547
548
549    add_documents(num_docs(), 2 * num_docs(), false),
550
551    QueryResult2 = try
552        query_reduce_view(DDocId, <<"test">>, false)
553    catch _:Error2 ->
554        Error2
555    end,
556    DocId = doc_id(num_docs()),
557    etap:is(QueryResult2,
558            {error, <<"Reducer: Error updating index for view `test`, reason: Value is not a number (key \"", DocId/binary, "\")">>},
559            "Received error response"),
560
561    receive
562    {'DOWN', MonRef, _, _, _} ->
563        etap:bail("view group died")
564    after 5000 ->
565        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
566    end,
567    couch_util:shutdown_sync(GroupPid),
568    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
569
570
571test_invalid_builtin_reduce_error() ->
572    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
573    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
574
575    DDocId = <<"_design/test">>,
576    DDoc = {[
577        {<<"meta">>, {[{<<"id">>, DDocId}]}},
578        {<<"json">>, {[
579        {<<"language">>, <<"javascript">>},
580        {<<"views">>, {[
581            {<<"test">>, {[
582                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 1); }">>},
583                {<<"reduce">>, <<"_foobar">>}
584            ]}}
585        ]}}
586        ]}}
587    ]},
588    Result = try
589        couch_set_view_test_util:update_ddoc(test_set_name(), DDoc)
590    catch throw:Error ->
591        Error
592    end,
593    ?etap_match(Result, {invalid_design_doc, _}, "Design document creation got rejected"),
594    {invalid_design_doc, Reason} = Result,
595    etap:diag("Design document creation error reason: " ++ binary_to_list(Reason)),
596
597    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
598
599
600test_reduce_runtime_error() ->
601    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
602    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
603
604    DDocId = <<"_design/test">>,
605    DDoc = {[
606        {<<"meta">>, {[{<<"id">>, DDocId}]}},
607        {<<"json">>, {[
608        {<<"language">>, <<"javascript">>},
609        {<<"views">>, {[
610            {<<"test">>, {[
611                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 1); }">>},
612                {<<"reduce">>, <<"function(key, values, rereduce) { return values[0].foo.bar; }">>}
613            ]}}
614        ]}}
615        ]}}
616    ]},
617    populate_set(DDoc),
618
619    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
620    GroupPid = couch_set_view:get_group_pid(
621        mapreduce_view, test_set_name(), DDocId, prod),
622    MonRef = erlang:monitor(process, GroupPid),
623
624    QueryResult = try
625        query_reduce_view(DDocId, <<"test">>, false)
626    catch _:Error ->
627        Error
628    end,
629    ?etap_match(QueryResult, {error, _}, "Received error response"),
630
631    receive
632    {'DOWN', MonRef, _, _, _} ->
633        etap:bail("view group died")
634    after 5000 ->
635        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
636    end,
637    couch_util:shutdown_sync(GroupPid),
638    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
639
640
641test_reduce_syntax_error() ->
642    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
643    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
644
645    DDocId = <<"_design/test">>,
646    DDoc = {[
647        {<<"meta">>, {[{<<"id">>, DDocId}]}},
648        {<<"json">>, {[
649        {<<"language">>, <<"javascript">>},
650        {<<"views">>, {[
651            {<<"test">>, {[
652                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 'foobar'); }">>},
653                {<<"reduce">>, <<"function(key, values, rereduce) { return sum(values);">>}
654            ]}}
655        ]}}
656        ]}}
657    ]},
658
659    Result = try
660        couch_set_view_test_util:update_ddoc(test_set_name(), DDoc)
661    catch throw:Error ->
662        Error
663    end,
664    ?etap_match(Result, {invalid_design_doc, _}, "Design document creation got rejected"),
665    {invalid_design_doc, Reason} = Result,
666    etap:diag("Design document creation error reason: " ++ binary_to_list(Reason)),
667
668    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
669
670
671test_reduce_too_large_reduction() ->
672    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
673    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
674
675    DDocId = <<"_design/test">>,
676    DDoc = {[
677        {<<"meta">>, {[{<<"id">>, DDocId}]}},
678        {<<"json">>, {[
679        {<<"views">>, {[
680            {<<"test">>, {[
681                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 'foobar'); }">>},
682                {<<"reduce">>, <<"function(key, values, rereduce) {"
683                                 "  if (rereduce) return 'foo';"
684                                 "  var r = 'qwerty';"
685                                 "  while (r.length < 65536) {"
686                                 "    r = r.concat(r);"
687                                 "  }"
688                                 "  return r;"
689                                 "}">>}
690            ]}}
691        ]}}
692        ]}}
693    ]},
694    populate_set(DDoc),
695
696    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
697    GroupPid = couch_set_view:get_group_pid(mapreduce_view, test_set_name(), DDocId, prod),
698    MonRef = erlang:monitor(process, GroupPid),
699
700    QueryResult = try
701        query_reduce_view(DDocId, <<"test">>, false)
702    catch _:Error ->
703        Error
704    end,
705
706    etap:is(QueryResult,
707            {error, <<"reduction too large">>},
708            "Received error response with too large reduce value"),
709
710    receive
711    {'DOWN', MonRef, _, _, _} ->
712        etap:bail("view group died")
713    after 5000 ->
714        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
715    end,
716    couch_util:shutdown_sync(GroupPid),
717    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
718
719
720test_reduce_too_large_rereduction() ->
721    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
722    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
723
724    DDocId = <<"_design/test">>,
725    DDoc = {[
726        {<<"meta">>, {[{<<"id">>, DDocId}]}},
727        {<<"json">>, {[
728        {<<"views">>, {[
729            {<<"test">>, {[
730                {<<"map">>, <<"function(doc, meta) { emit(meta.id, 'foobar'); }">>},
731                {<<"reduce">>, <<"function(key, values, rereduce) {"
732                                 "  if (!rereduce) return 'foo';"
733                                 "  var r = 'qwerty';"
734                                 "  while (r.length < 65536) {"
735                                 "    r = r.concat(r);"
736                                 "  }"
737                                 "  return r;"
738                                 "}">>}
739            ]}}
740        ]}}
741        ]}}
742    ]},
743    populate_set(DDoc),
744
745    ok = configure_view_group(DDocId, [0, 1, 2, 3], []),
746    GroupPid = couch_set_view:get_group_pid(mapreduce_view, test_set_name(), DDocId, prod),
747    MonRef = erlang:monitor(process, GroupPid),
748
749    QueryResult = try
750        query_reduce_view(DDocId, <<"test">>, false)
751    catch _:Error ->
752        Error
753    end,
754
755    etap:is(QueryResult,
756            {error, <<"reduction too large">>},
757            "Received error response with too large rereduce value"),
758
759    receive
760    {'DOWN', MonRef, _, _, _} ->
761        etap:bail("view group died")
762    after 5000 ->
763        etap:is(is_process_alive(GroupPid), true, "View group is still alive")
764    end,
765    couch_util:shutdown_sync(GroupPid),
766    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
767
768
769query_map_view(DDocId, ViewName, Stale) ->
770    etap:diag("Querying map view " ++ binary_to_list(DDocId) ++ "/" ++
771        binary_to_list(ViewName)),
772    {ok, View, Group, _} = couch_set_view:get_map_view(
773        test_set_name(), DDocId, ViewName, #set_view_group_req{stale = Stale}),
774    FoldFun = fun({{Key, DocId}, {_PartId, Value}}, _, Acc) ->
775        {ok, [{{Key, DocId}, Value} | Acc]}
776    end,
777    ViewArgs = #view_query_args{
778        run_reduce = true,
779        view_name = <<"test">>
780    },
781    {ok, _, Rows} = couch_set_view:fold(Group, View, FoldFun, [], ViewArgs),
782    couch_set_view:release_group(Group),
783    {ok, lists:reverse(Rows)}.
784
785
786query_reduce_view(DDocId, ViewName, Stale) ->
787    etap:diag("Querying reduce view " ++ binary_to_list(DDocId) ++ "/" ++
788        binary_to_list(ViewName) ++ "with ?group=true"),
789    {ok, View, Group, _} = couch_set_view:get_reduce_view(
790        test_set_name(), DDocId, ViewName, #set_view_group_req{stale = Stale}),
791    FoldFun = fun(Key, Red, Acc) -> {ok, [{Key, Red} | Acc]} end,
792    ViewArgs = #view_query_args{
793        run_reduce = true,
794        view_name = <<"test">>
795    },
796    {ok, Rows} = couch_set_view:fold_reduce(Group, View, FoldFun, [], ViewArgs),
797    couch_set_view:release_group(Group),
798    case Rows of
799    [{_Key, {json, RedValue}}] ->
800        {ok, RedValue};
801    [] ->
802        empty
803    end.
804
805
806populate_set(DDoc) ->
807    populate_set(DDoc, num_docs()).
808
809populate_set(DDoc, NumDocs) ->
810    etap:diag("Populating the " ++ integer_to_list(num_set_partitions()) ++
811        " databases with " ++ integer_to_list(num_docs()) ++ " documents"),
812    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
813    DocList = lists:map(
814        fun(I) ->
815            {[
816                {<<"meta">>, {[{<<"id">>, iolist_to_binary(["doc", integer_to_list(I)])}]}},
817                {<<"json">>, {[
818                    {<<"value">>, I}
819                ]}}
820            ]}
821        end,
822        lists:seq(1, NumDocs)),
823    ok = couch_set_view_test_util:populate_set_alternated(
824        test_set_name(),
825        lists:seq(0, num_set_partitions() - 1),
826        DocList).
827
828
829doc_id(K) ->
830    iolist_to_binary(io_lib:format("doc_~8..0b", [K])).
831
832
833add_documents(StartId, Count, IsNumber) ->
834    DocValFun = case IsNumber of
835    true ->
836        fun(K) -> K end;
837    _ ->
838        fun(K) -> doc_id(K) end
839    end,
840    etap:diag("Adding " ++ integer_to_list(Count) ++ " new documents"),
841    DocList0 = lists:map(
842        fun(I) ->
843            {I rem num_set_partitions(), {[
844                {<<"meta">>, {[{<<"id">>, doc_id(I)}]}},
845                {<<"json">>, {[
846                    {<<"value">>, DocValFun(I)}
847                ]}}
848            ]}}
849        end,
850        lists:seq(StartId, StartId + Count - 1)),
851
852    DocList = [Doc || {_, Doc} <- lists:keysort(1, DocList0)],
853    ok = couch_set_view_test_util:populate_set_sequentially(
854        test_set_name(),
855        lists:seq(0, num_set_partitions() - 1),
856        DocList).
857
858
859configure_view_group(DDocId, Active, Passive) ->
860    etap:diag("Configuring view group"),
861    Params = #set_view_params{
862        max_partitions = num_set_partitions(),
863        active_partitions = Active,
864        passive_partitions = Passive
865    },
866    try
867        couch_set_view:define_group(
868            mapreduce_view, test_set_name(), DDocId, Params)
869    catch _:Error ->
870        Error
871    end.
872