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    etap:plan(6),
29    case {run_test(false), run_test(true)} of
30    {ok, ok} ->
31        etap:end_tests();
32    Other ->
33        etap:diag(io_lib:format("test died abnormally: ~p", [Other])),
34        etap:bail(Other)
35    end,
36    ok.
37
38run_test(IsIPv6) ->
39    test_util:init_code_path(),
40    case (catch test(IsIPv6)) of
41        ok -> ok;
42        Other -> Other
43    end.
44
45test(IsIPv6) ->
46    couch_set_view_test_util:start_server(test_set_name(), IsIPv6),
47
48    etap:diag("Testing startup when header is invalid"),
49
50    setup_test(),
51    % Make sure there's a view created
52    {ok, {ViewResults}} = couch_set_view_test_util:query_view(
53        test_set_name(), ddoc_id(), <<"test">>, ["stale=false"]),
54    etap:is(ViewResults,
55        [{<<"total_rows">>,0},{<<"offset">>,0},{<<"rows">>,[]}],
56        "View created"),
57
58    % Create and append invalid header
59    Fd = get_fd(),
60    {ok, HeaderBin, _Pos} = couch_file:find_header_bin(Fd, eof),
61    <<Signature:16/binary, _HeaderBaseCompressed/binary>> = HeaderBin,
62    InvalidBase = couch_compress:compress(<<"this_is_not_a_valid_header">>),
63    InvalidHeaderBin = <<Signature:16/binary, InvalidBase/binary>>,
64    couch_file:write_header_bin(Fd, InvalidHeaderBin),
65
66    % Shutdown the group, so that it fails when it starts up again
67    couch_util:shutdown_sync(whereis(couch_setview_server_name_prod)),
68    % wait for server to come up
69    timer:sleep(2000),
70
71    % The first query leads to an error due to the invalid header
72    {ok, Body1} = couch_set_view_test_util:query_view(
73        test_set_name(), ddoc_id(), <<"test">>, ["stale=false"], 500),
74    etap:is(Body1, {[{<<"error">>, <<"badmatch">>},
75                     {<<"reason">>, <<"this_is_not_a_valid_header">>}]},
76           "First query leads to an error due to the invalid header"),
77    {ok, Body2} = couch_set_view_test_util:query_view(
78        test_set_name(), ddoc_id(), <<"test">>, ["stale=false"], 500),
79    % In case of MB-17044 it would be an `file_already_opened` error
80    etap:is(Body2, Body1,
81           "Second query leads to an error due to the invalid header as well"),
82
83    couch_set_view_test_util:stop_server(),
84    ok.
85
86
87setup_test() ->
88    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
89    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
90
91    DDoc = {[
92        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
93        {<<"json">>, {[
94            {<<"views">>, {[
95                {<<"test">>, {[
96                    {<<"map">>, <<"function(doc, meta) { emit(meta.id, doc.value); }">>}
97                ]}}
98            ]}}
99        ]}}
100    ]},
101    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
102    ok = configure_view_group(num_set_partitions()).
103
104
105configure_view_group(NumViewPartitions) ->
106    etap:diag("Configuring view group"),
107    Params = #set_view_params{
108        max_partitions = num_set_partitions(),
109        active_partitions = lists:seq(0, NumViewPartitions div 2),
110        passive_partitions = lists:seq(NumViewPartitions div 2 + 1, NumViewPartitions-1),
111        use_replica_index = true
112    },
113    try
114        couch_set_view:define_group(
115            mapreduce_view, test_set_name(), ddoc_id(), Params)
116    catch _:Error ->
117        Error
118    end.
119
120
121get_fd() ->
122    GroupPid = couch_set_view:get_group_pid(
123        mapreduce_view, test_set_name(), ddoc_id(), prod),
124    {ok, Group, 0} = gen_server:call(
125        GroupPid, #set_view_group_req{stale = ok, debug = true}, infinity),
126    Group#set_view_group.fd.
127