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% Like 08-deletes-cleanup.t but with several views in the design document.
20% This triggers a different codepath in the indexer.
21
22-define(JSON_ENCODE(V), ejson:encode(V)). % couch_db.hrl
23-define(MAX_WAIT_TIME, 900 * 1000).
24
25% from couch_db.hrl
26-define(MIN_STR, <<>>).
27-define(MAX_STR, <<255>>).
28
29-record(view_query_args, {
30    start_key,
31    end_key,
32    start_docid = ?MIN_STR,
33    end_docid = ?MAX_STR,
34    direction = fwd,
35    inclusive_end = true,
36    limit = 10000000000,
37    skip = 0,
38    group_level = 0,
39    view_type = nil,
40    include_docs = false,
41    conflicts = false,
42    stale = false,
43    multi_get = false,
44    callback = nil,
45    list = nil,
46    run_reduce = true,
47    keys = nil,
48    view_name = nil,
49    debug = false,
50    filter = true,
51    type = main
52}).
53
54
55test_set_name() -> <<"couch_test_set_index_deletes_cleanup_many">>.
56num_set_partitions() -> 64.
57ddoc_id() -> <<"_design/test">>.
58initial_num_docs() -> 156288.  % must be multiple of num_set_partitions()
59
60
61main(_) ->
62    test_util:init_code_path(),
63
64    etap:plan(154),
65    case (catch test()) of
66        ok ->
67            etap:end_tests();
68        Other ->
69            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
70            etap:bail(Other)
71    end,
72    ok.
73
74
75test() ->
76    couch_set_view_test_util:start_server(test_set_name()),
77
78    create_set(),
79    add_documents(0, initial_num_docs()),
80
81    ExpectedView2Value1 = lists:sum(lists:seq(0, initial_num_docs() - 1)),
82    ExpectedView3Value1 = lists:sum(lists:seq(0, initial_num_docs() - 1)) * 4,
83
84    {QueryResult1View1, Group1} = query_reduce_view(<<"view_1">>, false),
85    etap:is(
86        QueryResult1View1,
87        initial_num_docs(),
88        "Reduce value of view 1 is " ++ couch_util:to_list(initial_num_docs())),
89    {QueryResult1View2, Group2} = query_reduce_view(<<"view_2">>, false),
90    etap:is(
91        QueryResult1View2,
92        ExpectedView2Value1,
93        "Reduce value of view 2 is " ++ couch_util:to_list(ExpectedView2Value1)),
94    {QueryResult1View3, Group3} = query_reduce_view(<<"view_3">>, false),
95    etap:is(
96        QueryResult1View3,
97        ExpectedView3Value1,
98        "Reduce value of view 3 is " ++ couch_util:to_list(ExpectedView3Value1)),
99
100    verify_btrees_1(Group1),
101    compare_groups(Group1, Group2),
102    compare_groups(Group1, Group3),
103
104    compact_view_group(),
105
106    {QueryResult2View1, Group4} = query_reduce_view(<<"view_1">>, false),
107    etap:is(
108        QueryResult2View1,
109        initial_num_docs(),
110        "Reduce value of view 1 is " ++ couch_util:to_list(initial_num_docs())),
111    {QueryResult2View2, Group5} = query_reduce_view(<<"view_2">>, false),
112    etap:is(
113        QueryResult2View2,
114        ExpectedView2Value1,
115        "Reduce value of view 2 is " ++ couch_util:to_list(ExpectedView2Value1)),
116    {QueryResult2View3, Group6} = query_reduce_view(<<"view_3">>, false),
117    etap:is(
118        QueryResult2View3,
119        ExpectedView3Value1,
120        "Reduce value of view 3 is " ++ couch_util:to_list(ExpectedView3Value1)),
121
122    verify_btrees_1(Group4),
123    compare_groups(Group4, Group5),
124    compare_groups(Group4, Group6),
125
126    etap:diag("Deleting all documents"),
127    delete_docs(0, initial_num_docs()),
128    etap:is(
129        couch_set_view_test_util:doc_count(test_set_name(), lists:seq(0, 63)),
130        0,
131        "All docs were deleted"),
132
133    etap:diag("Marking partitions [ 32 .. 63 ] for cleanup"),
134    ok = lists:foreach(
135        fun(I) ->
136            ok = couch_set_view:set_partition_states(
137                mapreduce_view, test_set_name(), ddoc_id(), [], [], [I])
138        end,
139        lists:seq(32, 63)),
140
141    etap:diag("Waiting for cleanup of partitions [ 32 .. 63 ]"),
142    MainGroupInfo = get_group_info(),
143    wait_for_cleanup(MainGroupInfo),
144    etap:diag("Cleanup finished"),
145
146    {QueryResult3View1, Group7} = query_reduce_view(<<"view_1">>, false),
147    etap:is(
148        QueryResult3View1,
149        empty,
150        "Reduce value of view 1 is empty"),
151    {QueryResult3View2, Group8} = query_reduce_view(<<"view_2">>, false),
152    etap:is(
153        QueryResult3View2,
154        empty,
155        "Reduce value of view 2 is empty"),
156    {QueryResult3View3, Group9} = query_reduce_view(<<"view_3">>, false),
157    etap:is(
158        QueryResult3View3,
159        empty,
160        "Reduce value of view 3 is empty"),
161
162    verify_btrees_2(Group7),
163    compare_groups(Group7, Group8),
164    compare_groups(Group7, Group9),
165
166    compact_view_group(),
167
168    {QueryResult4View1, Group10} = query_reduce_view(<<"view_1">>, false),
169    etap:is(
170        QueryResult4View1,
171        empty,
172        "Reduce value of view 1 is empty"),
173    {QueryResult4View2, Group11} = query_reduce_view(<<"view_2">>, false),
174    etap:is(
175        QueryResult4View2,
176        empty,
177        "Reduce value of view 2 is empty"),
178    {QueryResult4View3, Group12} = query_reduce_view(<<"view_3">>, false),
179    etap:is(
180        QueryResult4View3,
181        empty,
182        "Reduce value of view 3 is empty"),
183
184    verify_btrees_2(Group10),
185    compare_groups(Group10, Group11),
186    compare_groups(Group10, Group12),
187
188    etap:diag("Marking partitions [ 32 .. 63 ] as active"),
189    ok = lists:foreach(
190        fun(I) ->
191            ok = couch_set_view:set_partition_states(
192                mapreduce_view, test_set_name(), ddoc_id(), [I], [], [])
193        end,
194        lists:seq(32, 63)),
195
196    {QueryResult5View1, Group13} = query_reduce_view(<<"view_1">>, false),
197    etap:is(
198        QueryResult5View1,
199        empty,
200        "Reduce value of view 1 is empty"),
201    {QueryResult5View2, Group14} = query_reduce_view(<<"view_2">>, false),
202    etap:is(
203        QueryResult5View2,
204        empty,
205        "Reduce value of view 2 is empty"),
206    {QueryResult5View3, Group15} = query_reduce_view(<<"view_3">>, false),
207    etap:is(
208        QueryResult5View3,
209        empty,
210        "Reduce value of view 3 is empty"),
211
212    verify_btrees_3(Group13),
213    compare_groups(Group13, Group14),
214    compare_groups(Group13, Group15),
215
216    compact_view_group(),
217
218    {QueryResult6View1, Group16} = query_reduce_view(<<"view_1">>, false),
219    etap:is(
220        QueryResult6View1,
221        empty,
222        "Reduce value of view 1 is empty"),
223    {QueryResult6View2, Group17} = query_reduce_view(<<"view_2">>, false),
224    etap:is(
225        QueryResult6View2,
226        empty,
227        "Reduce value of view 2 is empty"),
228    {QueryResult6View3, Group18} = query_reduce_view(<<"view_3">>, false),
229    etap:is(
230        QueryResult6View3,
231        empty,
232        "Reduce value of view 3 is empty"),
233
234    verify_btrees_3(Group16),
235    compare_groups(Group16, Group17),
236    compare_groups(Group16, Group18),
237
238    etap:diag("Creating the same documents again"),
239    add_documents(0, initial_num_docs()),
240
241    {QueryResult7View1, Group19} = query_reduce_view(<<"view_1">>, false),
242    etap:is(
243        QueryResult7View1,
244        initial_num_docs(),
245        "Reduce value of view 1 is " ++ couch_util:to_list(initial_num_docs())),
246    {QueryResult7View2, Group20} = query_reduce_view(<<"view_2">>, false),
247    etap:is(
248        QueryResult7View2,
249        ExpectedView2Value1,
250        "Reduce value of view 2 is " ++ couch_util:to_list(ExpectedView2Value1)),
251    {QueryResult7View3, Group21} = query_reduce_view(<<"view_3">>, false),
252    etap:is(
253        QueryResult7View3,
254        ExpectedView3Value1,
255        "Reduce value of view 3 is " ++ couch_util:to_list(ExpectedView3Value1)),
256
257    verify_btrees_1(Group19),
258    compare_groups(Group19, Group20),
259    compare_groups(Group19, Group21),
260
261    compact_view_group(),
262
263    {QueryResult8View1, Group22} = query_reduce_view(<<"view_1">>, false),
264    etap:is(
265        QueryResult8View1,
266        initial_num_docs(),
267        "Reduce value of view 1 is " ++ couch_util:to_list(initial_num_docs())),
268    {QueryResult8View2, Group23} = query_reduce_view(<<"view_2">>, false),
269    etap:is(
270        QueryResult8View2,
271        ExpectedView2Value1,
272        "Reduce value of view 2 is " ++ couch_util:to_list(ExpectedView2Value1)),
273    {QueryResult8View3, Group24} = query_reduce_view(<<"view_3">>, false),
274    etap:is(
275        QueryResult8View3,
276        ExpectedView3Value1,
277        "Reduce value of view 3 is " ++ couch_util:to_list(ExpectedView3Value1)),
278
279    verify_btrees_1(Group22),
280    compare_groups(Group22, Group23),
281    compare_groups(Group22, Group24),
282
283    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
284    couch_set_view_test_util:stop_server(),
285    ok.
286
287
288query_reduce_view(ViewName, Stale) ->
289    etap:diag("Querying reduce view " ++ binary_to_list(ViewName) ++ " with ?group=true"),
290    {ok, View, Group, _} = couch_set_view:get_reduce_view(
291        test_set_name(), ddoc_id(), ViewName,
292        #set_view_group_req{stale = Stale, debug = true}),
293    FoldFun = fun(Key, Red, Acc) -> {ok, [{Key, Red} | Acc]} end,
294    ViewArgs = #view_query_args{
295        run_reduce = true,
296        view_name = ViewName
297    },
298    {ok, Rows} = couch_set_view:fold_reduce(Group, View, FoldFun, [], ViewArgs),
299    couch_set_view:release_group(Group),
300    case Rows of
301    [{_Key, {json, RedValue}}] ->
302        {ejson:decode(RedValue), Group};
303    [] ->
304        {empty, Group}
305    end.
306
307
308wait_for_cleanup(GroupInfo) ->
309    etap:diag("Waiting for main index cleanup to finish"),
310    Pid = spawn(fun() ->
311        wait_for_cleanup_loop(GroupInfo)
312    end),
313    Ref = erlang:monitor(process, Pid),
314    receive
315    {'DOWN', Ref, process, Pid, normal} ->
316        ok;
317    {'DOWN', Ref, process, Pid, noproc} ->
318        ok;
319    {'DOWN', Ref, process, Pid, Reason} ->
320        etap:bail("Failure waiting for main index cleanup: " ++ couch_util:to_list(Reason))
321    after ?MAX_WAIT_TIME ->
322        etap:bail("Timeout waiting for main index cleanup")
323    end.
324
325
326wait_for_cleanup_loop(GroupInfo) ->
327    case couch_util:get_value(cleanup_partitions, GroupInfo) of
328    [] ->
329        {Stats} = couch_util:get_value(stats, GroupInfo),
330        Cleanups = couch_util:get_value(cleanups, Stats),
331        etap:is(
332            (is_integer(Cleanups) andalso (Cleanups > 0)),
333            true,
334            "Main group stats has at least 1 full cleanup");
335    _ ->
336        ok = timer:sleep(1000),
337        wait_for_cleanup_loop(get_group_info())
338    end.
339
340
341get_group_info() ->
342    {ok, Info} = couch_set_view:get_group_info(
343        mapreduce_view, test_set_name(), ddoc_id(), prod),
344    Info.
345
346
347delete_docs(StartId, NumDocs) ->
348    Dbs = dict:from_list(lists:map(
349        fun(I) ->
350            {ok, Db} = couch_set_view_test_util:open_set_db(test_set_name(), I),
351            {I, Db}
352        end,
353        lists:seq(0, 63))),
354    Docs = lists:foldl(
355        fun(I, Acc) ->
356            Doc = couch_doc:from_json_obj({[
357                {<<"meta">>, {[{<<"deleted">>, true}, {<<"id">>, doc_id(I)}]}},
358                {<<"json">>, {[]}}
359            ]}),
360            DocList = case orddict:find(I rem 64, Acc) of
361            {ok, L} ->
362                L;
363            error ->
364                []
365            end,
366            orddict:store(I rem 64, [Doc | DocList], Acc)
367        end,
368        orddict:new(), lists:seq(StartId, StartId + NumDocs - 1)),
369    [] = orddict:fold(
370        fun(I, DocList, Acc) ->
371            Db = dict:fetch(I, Dbs),
372            etap:diag("Deleting " ++ integer_to_list(length(DocList)) ++
373                " documents from partition " ++ integer_to_list(I)),
374            ok = couch_db:update_docs(Db, DocList, [sort_docs]),
375            Acc
376        end,
377        [], Docs),
378    ok = lists:foreach(fun({_, Db}) -> ok = couch_db:close(Db) end, dict:to_list(Dbs)).
379
380
381create_set() ->
382    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
383    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
384    couch_set_view:cleanup_index_files(mapreduce_view, test_set_name()),
385    etap:diag("Creating the set databases (# of partitions: " ++
386        integer_to_list(num_set_partitions()) ++ ")"),
387    DDoc = {[
388        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
389        {<<"json">>, {[
390        {<<"language">>, <<"javascript">>},
391        {<<"views">>, {[
392            {<<"view_1">>, {[
393                {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>},
394                {<<"reduce">>, <<"_count">>}
395            ]}},
396            {<<"view_2">>, {[
397                {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>},
398                {<<"reduce">>, <<"_sum">>}
399            ]}},
400            {<<"view_3">>, {[
401                {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value * 2); }">>},
402                {<<"reduce">>, <<"function(key, values, rereduce) {"
403                                 "if (rereduce) {"
404                                 "    return sum(values);"
405                                 "} else {"
406                                 "    var result = 0;"
407                                 "    for (var i = 0; i < values.length; i++) {"
408                                 "        result += (values[i] * 2);"
409                                 "    }"
410                                 "    return result;"
411                                 "}"
412                                 "}">>}
413            ]}}
414        ]}}
415        ]}}
416    ]},
417    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
418    etap:diag("Configuring set view with partitions [0 .. 63] as active"),
419    Params = #set_view_params{
420        max_partitions = num_set_partitions(),
421        active_partitions = lists:seq(0, 63),
422        passive_partitions = [],
423        use_replica_index = false
424    },
425    ok = couch_set_view:define_group(
426        mapreduce_view, test_set_name(), ddoc_id(), Params).
427
428
429add_documents(StartId, Count) ->
430    etap:diag("Adding " ++ integer_to_list(Count) ++ " new documents"),
431    DocList0 = lists:map(
432        fun(I) ->
433            {I rem num_set_partitions(), {[
434            {<<"meta">>, {[{<<"id">>, doc_id(I)}]}},
435            {<<"json">>, {[
436                {<<"value">>, I}
437            ]}}
438            ]}}
439        end,
440        lists:seq(StartId, StartId + Count - 1)),
441    DocList = [Doc || {_, Doc} <- lists:keysort(1, DocList0)],
442    ok = couch_set_view_test_util:populate_set_sequentially(
443        test_set_name(),
444        lists:seq(0, num_set_partitions() - 1),
445        DocList).
446
447
448doc_id(I) ->
449    iolist_to_binary(io_lib:format("doc_~8..0b", [I])).
450
451
452compact_view_group() ->
453    {ok, CompactPid} = couch_set_view_compactor:start_compact(
454        mapreduce_view, test_set_name(), ddoc_id(), main),
455    Ref = erlang:monitor(process, CompactPid),
456    etap:diag("Waiting for view group compaction to finish"),
457    receive
458    {'DOWN', Ref, process, CompactPid, normal} ->
459        ok;
460    {'DOWN', Ref, process, CompactPid, noproc} ->
461        ok;
462    {'DOWN', Ref, process, CompactPid, Reason} ->
463        etap:bail("Failure compacting main group: " ++ couch_util:to_list(Reason))
464    after ?MAX_WAIT_TIME ->
465        etap:bail("Timeout waiting for main group compaction to finish")
466    end.
467
468
469get_view(_ViewName, []) ->
470    undefined;
471get_view(ViewName, [SetView | Rest]) ->
472    RedFuns = (SetView#set_view.indexer)#mapreduce_view.reduce_funs,
473    case couch_util:get_value(ViewName, RedFuns) of
474    undefined ->
475        get_view(ViewName, Rest);
476    _ ->
477        SetView
478    end.
479
480
481verify_btrees_1(Group) ->
482    #set_view_group{
483        id_btree = IdBtree,
484        views = Views,
485        index_header = #set_view_index_header{
486            seqs = HeaderUpdateSeqs,
487            abitmask = Abitmask,
488            pbitmask = Pbitmask,
489            cbitmask = Cbitmask
490        }
491    } = Group,
492    etap:is(2, length(Views), "2 view btrees in the group"),
493    View0 = get_view(<<"view_1">>, Views),
494    View1 = get_view(<<"view_2">>, Views),
495    View2 = get_view(<<"view_3">>, Views),
496    etap:is(View1, View0, "Views 1 and 2 share the same btree"),
497    #set_view{
498        indexer = #mapreduce_view{
499            btree = View0Btree
500        }
501    } = View0,
502    #set_view{
503        indexer = #mapreduce_view{
504            btree = View2Btree
505        }
506    } = View2,
507    etap:diag("Verifying view group btrees"),
508    ExpectedBitmask = couch_set_view_util:build_bitmask(lists:seq(0, 63)),
509    DbSeqs = couch_set_view_test_util:get_db_seqs(test_set_name(), lists:seq(0, 63)),
510
511    etap:is(
512        couch_set_view_test_util:full_reduce_id_btree(Group, IdBtree),
513        {ok, {initial_num_docs(), ExpectedBitmask}},
514        "Id Btree has the right reduce value"),
515    ExpectedView0Reds = [lists:sum(lists:seq(0, initial_num_docs() - 1)), initial_num_docs()],
516    etap:is(
517        couch_set_view_test_util:full_reduce_view_btree(Group, View0Btree),
518        {ok, {initial_num_docs(), ExpectedView0Reds, ExpectedBitmask}},
519        "View0 Btree has the right reduce value"),
520    ExpectedView2Reds = [lists:sum(lists:seq(0, initial_num_docs() - 1)) * 4],
521    etap:is(
522        couch_set_view_test_util:full_reduce_view_btree(Group, View2Btree),
523        {ok, {initial_num_docs(), ExpectedView2Reds, ExpectedBitmask}},
524        "View2 Btree has the right reduce value"),
525
526    etap:is(HeaderUpdateSeqs, DbSeqs, "Header has right update seqs list"),
527    etap:is(Abitmask, ExpectedBitmask, "Header has right active bitmask"),
528    etap:is(Pbitmask, 0, "Header has right passive bitmask"),
529    etap:is(Cbitmask, 0, "Header has right cleanup bitmask"),
530
531    etap:diag("Verifying the Id Btree"),
532    MaxPerPart = initial_num_docs() div num_set_partitions(),
533    {ok, _, {_, _, _, IdBtreeFoldResult}} = couch_set_view_test_util:fold_id_btree(
534        Group,
535        IdBtree,
536        fun(Kv, _, {P0, I0, C0, It}) ->
537            case C0 >= MaxPerPart of
538            true ->
539                P = P0 + 1,
540                I = P,
541                C = 1;
542            false ->
543                P = P0,
544                I = I0,
545                C = C0 + 1
546            end,
547            true = (P < num_set_partitions()),
548            Value = [{View2#set_view.id_num, doc_id(I)}, {View0#set_view.id_num, doc_id(I)}],
549            ExpectedKv = {<<P:16, (doc_id(I))/binary>>, {P, Value}},
550            case ExpectedKv =:= Kv of
551            true ->
552                ok;
553            false ->
554                etap:bail("Id Btree has an unexpected KV at iteration " ++ integer_to_list(It))
555            end,
556            {ok, {P, I + num_set_partitions(), C, It + 1}}
557        end,
558        {0, 0, 0, 0}, []),
559    etap:is(IdBtreeFoldResult, initial_num_docs(),
560        "Id Btree has " ++ integer_to_list(initial_num_docs()) ++ " entries"),
561    etap:diag("Verifying the View0 Btree"),
562    {ok, _, View0BtreeFoldResult} = couch_set_view_test_util:fold_view_btree(
563        Group,
564        View0Btree,
565        fun(Kv, _, Acc) ->
566            ExpectedKv = {{doc_id(Acc), doc_id(Acc)}, {Acc rem 64, Acc}},
567            case ExpectedKv =:= Kv of
568            true ->
569                ok;
570            false ->
571                etap:bail("View0 Btree has an unexpected KV at iteration " ++ integer_to_list(Acc))
572            end,
573            {ok, Acc + 1}
574        end,
575        0, []),
576    etap:is(View0BtreeFoldResult, initial_num_docs(),
577        "View0 Btree has " ++ integer_to_list(initial_num_docs()) ++ " entries"),
578    etap:diag("Verifying the View2 Btree"),
579    {ok, _, View2BtreeFoldResult} =  couch_set_view_test_util:fold_view_btree(
580        Group,
581        View2Btree,
582        fun(Kv, _, Acc) ->
583            ExpectedKv = {{doc_id(Acc), doc_id(Acc)}, {Acc rem 64, Acc * 2}},
584            case ExpectedKv =:= Kv of
585            true ->
586                ok;
587            false ->
588                etap:bail("View2 Btree has an unexpected KV at iteration " ++ integer_to_list(Acc))
589            end,
590            {ok, Acc + 1}
591        end,
592        0, []),
593    etap:is(View2BtreeFoldResult, initial_num_docs(),
594        "View2 Btree has " ++ integer_to_list(initial_num_docs()) ++ " entries").
595
596
597verify_btrees_2(Group) ->
598    #set_view_group{
599        id_btree = IdBtree,
600        views = Views,
601        index_header = #set_view_index_header{
602            seqs = HeaderUpdateSeqs,
603            abitmask = Abitmask,
604            pbitmask = Pbitmask,
605            cbitmask = Cbitmask
606        }
607    } = Group,
608    etap:is(2, length(Views), "2 view btrees in the group"),
609    View0 = get_view(<<"view_1">>, Views),
610    View1 = get_view(<<"view_2">>, Views),
611    View2 = get_view(<<"view_3">>, Views),
612    etap:is(View1, View0, "Views 1 and 2 share the same btree"),
613    #set_view{
614        indexer = #mapreduce_view{
615            btree = View0Btree
616        }
617    } = View0,
618    #set_view{
619        indexer = #mapreduce_view{
620            btree = View2Btree
621        }
622    } = View2,
623    etap:diag("Verifying view group btrees"),
624    ExpectedABitmask = couch_set_view_util:build_bitmask(lists:seq(0, 31)),
625    DbSeqs = couch_set_view_test_util:get_db_seqs(test_set_name(), lists:seq(0, 31)),
626
627    etap:is(
628        couch_set_view_test_util:full_reduce_id_btree(Group, IdBtree),
629        {ok, {0, 0}},
630        "Id Btree has the right reduce value"),
631    etap:is(
632        couch_set_view_test_util:full_reduce_view_btree(Group, View0Btree),
633        {ok, {0, [0, 0], 0}},
634        "View0 Btree has the right reduce value"),
635    etap:is(
636        couch_set_view_test_util:full_reduce_view_btree(Group, View2Btree),
637        {ok, {0, [0], 0}},
638        "View2 Btree has the right reduce value"),
639
640    etap:is(HeaderUpdateSeqs, DbSeqs, "Header has right update seqs list"),
641    etap:is(Abitmask, ExpectedABitmask, "Header has right active bitmask"),
642    etap:is(Pbitmask, 0, "Header has right passive bitmask"),
643    etap:is(Cbitmask, 0, "Header has right cleanup bitmask"),
644
645    etap:diag("Verifying the Id Btree"),
646    {ok, _, IdBtreeFoldResult} = couch_set_view_test_util:fold_id_btree(
647        Group,
648        IdBtree,
649        fun(_Kv, _, Acc) ->
650            {ok, Acc + 1}
651        end,
652        0, []),
653    etap:is(IdBtreeFoldResult, 0, "Id Btree is empty"),
654    etap:diag("Verifying the View0 Btree"),
655    {ok, _, View0BtreeFoldResult} = couch_set_view_test_util:fold_view_btree(
656        Group,
657        View0Btree,
658        fun(_Kv, _, Acc) ->
659            {ok, Acc + 1}
660        end,
661        0, []),
662    etap:is(View0BtreeFoldResult, 0, "View0 Btree is empty"),
663    etap:diag("Verifying the View2 Btree"),
664    {ok, _, View2BtreeFoldResult} = couch_set_view_test_util:fold_view_btree(
665        Group,
666        View2Btree,
667        fun(_Kv, _, Acc) ->
668            {ok, Acc + 1}
669        end,
670        0, []),
671    etap:is(View2BtreeFoldResult, 0, "View2 Btree is empty").
672
673
674verify_btrees_3(Group) ->
675    #set_view_group{
676        id_btree = IdBtree,
677        views = Views,
678        index_header = #set_view_index_header{
679            seqs = HeaderUpdateSeqs,
680            abitmask = Abitmask,
681            pbitmask = Pbitmask,
682            cbitmask = Cbitmask
683        }
684    } = Group,
685    etap:is(2, length(Views), "2 view btrees in the group"),
686    View0 = get_view(<<"view_1">>, Views),
687    View1 = get_view(<<"view_2">>, Views),
688    View2 = get_view(<<"view_3">>, Views),
689    etap:is(View1, View0, "Views 1 and 2 share the same btree"),
690    #set_view{
691        indexer = #mapreduce_view{
692            btree = View0Btree
693        }
694    } = View0,
695    #set_view{
696        indexer = #mapreduce_view{
697            btree = View2Btree
698        }
699    } = View2,
700    etap:diag("Verifying view group btrees"),
701    ExpectedABitmask = couch_set_view_util:build_bitmask(lists:seq(0, 63)),
702    DbSeqs = couch_set_view_test_util:get_db_seqs(test_set_name(), lists:seq(0, 63)),
703
704    etap:is(
705        couch_set_view_test_util:full_reduce_id_btree(Group, IdBtree),
706        {ok, {0, 0}},
707        "Id Btree has the right reduce value"),
708    etap:is(
709        couch_set_view_test_util:full_reduce_view_btree(Group, View0Btree),
710        {ok, {0, [0, 0], 0}},
711        "View0 Btree has the right reduce value"),
712    etap:is(
713        couch_set_view_test_util:full_reduce_view_btree(Group, View2Btree),
714        {ok, {0, [0], 0}},
715        "View2 Btree has the right reduce value"),
716
717    etap:is(HeaderUpdateSeqs, DbSeqs, "Header has right update seqs list"),
718    etap:is(Abitmask, ExpectedABitmask, "Header has right active bitmask"),
719    etap:is(Pbitmask, 0, "Header has right passive bitmask"),
720    etap:is(Cbitmask, 0, "Header has right cleanup bitmask"),
721
722    etap:diag("Verifying the Id Btree"),
723    {ok, _, IdBtreeFoldResult} = couch_set_view_test_util:fold_id_btree(
724        Group,
725        IdBtree,
726        fun(_Kv, _, Acc) ->
727            {ok, Acc + 1}
728        end,
729        0, []),
730    etap:is(IdBtreeFoldResult, 0, "Id Btree is empty"),
731    etap:diag("Verifying the View0 Btree"),
732    {ok, _, View0BtreeFoldResult} = couch_set_view_test_util:fold_view_btree(
733        Group,
734        View0Btree,
735        fun(_Kv, _, Acc) ->
736            {ok, Acc + 1}
737        end,
738        0, []),
739    etap:is(View0BtreeFoldResult, 0, "View0 Btree is empty"),
740    etap:diag("Verifying the View2 Btree"),
741    {ok, _, View2BtreeFoldResult} = couch_set_view_test_util:fold_view_btree(
742        Group,
743        View0Btree,
744        fun(_Kv, _, Acc) ->
745            {ok, Acc + 1}
746        end,
747        0, []),
748    etap:is(View2BtreeFoldResult, 0, "View2 Btree is empty").
749
750
751compare_groups(Group1, Group2) ->
752    etap:is(
753        Group2#set_view_group.views,
754        Group1#set_view_group.views,
755        "View states are equal"),
756    etap:is(
757        Group2#set_view_group.index_header,
758        Group1#set_view_group.index_header,
759        "Index headers are equal").
760