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% This test is about a corrupted file on group startup. This lead to
18% dangling references in the couch_file_write_guard.
19
20-include_lib("couch_set_view/include/couch_set_view.hrl").
21
22test_set_name() -> <<"couch_test_set_view_write_guard">>.
23num_set_partitions() -> 4.
24ddoc_id() -> <<"_design/test">>.
25
26
27main(_) ->
28    test_util:init_code_path(),
29
30    etap:plan(3),
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    %init:stop(),
39    %receive after infinity -> ok end,
40    ok.
41
42
43test() ->
44    couch_set_view_test_util:start_server(test_set_name()),
45
46    etap:diag("Testing startup when header is invalid"),
47
48    setup_test(),
49    % Make sure there's a view created
50    {ok, {ViewResults}} = couch_set_view_test_util:query_view(
51        test_set_name(), ddoc_id(), <<"test">>, ["stale=false"]),
52    etap:is(ViewResults,
53        [{<<"total_rows">>,0},{<<"offset">>,0},{<<"rows">>,[]}],
54        "View created"),
55
56    % Create and append invalid header
57    Fd = get_fd(),
58    {ok, HeaderBin, _Pos} = couch_file:find_header_bin(Fd, eof),
59    <<Signature:16/binary, _HeaderBaseCompressed/binary>> = HeaderBin,
60    InvalidBase = couch_compress:compress(<<"this_is_not_a_valid_header">>),
61    InvalidHeaderBin = <<Signature:16/binary, InvalidBase/binary>>,
62    couch_file:write_header_bin(Fd, InvalidHeaderBin),
63
64    % Shutdown the group, so that it fails when it starts up again
65    couch_util:shutdown_sync(whereis(couch_setview_server_name_prod)),
66
67    % The first query leads to an error due to the invalid header
68    {ok, Body1} = couch_set_view_test_util:query_view(
69        test_set_name(), ddoc_id(), <<"test">>, ["stale=false"], 500),
70    etap:is(Body1, {[{<<"error">>, <<"badmatch">>},
71                     {<<"reason">>, <<"this_is_not_a_valid_header">>}]},
72           "First query leads to an error due to the invalid header"),
73    {ok, Body2} = couch_set_view_test_util:query_view(
74        test_set_name(), ddoc_id(), <<"test">>, ["stale=false"], 500),
75    % In case of MB-17044 it would be an `file_already_opened` error
76    etap:is(Body2, Body1,
77           "Second query leads to an error due to the invalid header as well"),
78
79    couch_set_view_test_util:stop_server(),
80    ok.
81
82
83setup_test() ->
84    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
85    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
86
87    DDoc = {[
88        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
89        {<<"json">>, {[
90            {<<"views">>, {[
91                {<<"test">>, {[
92                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>}
93                ]}}
94            ]}}
95        ]}}
96    ]},
97    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
98    ok = configure_view_group(num_set_partitions()).
99
100
101configure_view_group(NumViewPartitions) ->
102    etap:diag("Configuring view group"),
103    Params = #set_view_params{
104        max_partitions = num_set_partitions(),
105        active_partitions = lists:seq(0, NumViewPartitions div 2),
106        passive_partitions = lists:seq(NumViewPartitions div 2 + 1, NumViewPartitions-1),
107        use_replica_index = true
108    },
109    try
110        couch_set_view:define_group(
111            mapreduce_view, test_set_name(), ddoc_id(), Params)
112    catch _:Error ->
113        Error
114    end.
115
116
117get_fd() ->
118    GroupPid = couch_set_view:get_group_pid(
119        mapreduce_view, test_set_name(), ddoc_id(), prod),
120    {ok, Group, 0} = gen_server:call(
121        GroupPid, #set_view_group_req{stale = ok, debug = true}, infinity),
122    Group#set_view_group.fd.
123