blob: a2fffb446d9355b0682f9c46032f7d0896bef6b7 [file]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couch_query_servers_tests).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch/include/couch_eunit.hrl").
setup() ->
meck:new(couch_log, [passthrough]),
Ctx = test_util:start_couch([ioq]),
config:set("query_server_config", "reduce_limit", "true", false),
config:set("log", "level", "info", false),
Ctx.
teardown(Ctx) ->
config:delete("query_server_config", "reduce_limit", true),
config:delete("query_server_config", "reduce_limit_threshold", true),
config:delete("query_server_config", "reduce_limit_ratio", true),
config:delete("log", "level", false),
test_util:stop_couch(Ctx),
meck:unload().
query_server_limits_test_() ->
{
"Test overflow detection and batch splitting in query server",
{
foreach,
fun setup/0,
fun teardown/1,
[
?TDEF_FE(builtin_should_return_error_on_overflow),
?TDEF_FE(builtin_should_not_return_error_with_generous_overflow_threshold),
?TDEF_FE(builtin_should_not_return_error_with_generous_overflow_ratio),
?TDEF_FE(builtin_should_return_object_on_log),
?TDEF_FE(builtin_should_return_object_on_false),
?TDEF_FE(js_reduce_should_return_error_on_overflow),
?TDEF_FE(js_reduce_should_return_object_on_log),
?TDEF_FE(js_reduce_should_return_object_on_false),
?TDEF_FE(should_split_large_batches)
]
}
}.
builtin_should_return_error_on_overflow(_) ->
config:set("query_server_config", "reduce_limit", "true", false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"foo">>, [<<"_sum">>], KVs),
?assertMatch({[{<<"error">>, <<"builtin_reduce_error">>} | _]}, Result),
?assert(meck:called(couch_log, error, '_')).
builtin_should_not_return_error_with_generous_overflow_threshold(_) ->
config:set("query_server_config", "reduce_limit", "true", false),
config:set_integer("query_server_config", "reduce_limit_threshold", 1000000, false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"foo">>, [<<"_sum">>], KVs),
?assertNotMatch({[{<<"error">>, <<"builtin_reduce_error">>} | _]}, Result).
builtin_should_not_return_error_with_generous_overflow_ratio(_) ->
config:set("query_server_config", "reduce_limit", "true", false),
config:set_float("query_server_config", "reduce_limit_ratio", 0.1, false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"foo">>, [<<"_sum">>], KVs),
?assertNotMatch({[{<<"error">>, <<"builtin_reduce_error">>} | _]}, Result).
builtin_should_return_object_on_log(_) ->
config:set("query_server_config", "reduce_limit", "log", false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"foo">>, [<<"_sum">>], KVs),
?assertMatch({[_ | _]}, Result),
Keys = [K || {K, _} <- element(1, Result)],
?assert(not lists:member(<<"error">>, Keys)),
?assert(meck:called(couch_log, error, '_')).
builtin_should_return_object_on_false(_) ->
config:set("query_server_config", "reduce_limit", "false", false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"foo">>, [<<"_sum">>], KVs),
?assertMatch({[_ | _]}, Result),
Keys = [K || {K, _} <- element(1, Result)],
?assert(not lists:member(<<"error">>, Keys)),
?assertNot(meck:called(couch_log, error, '_')).
js_reduce_should_return_error_on_overflow(_) ->
config:set("query_server_config", "reduce_limit", "true", false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"javascript">>, [sum_js()], KVs),
?assertMatch({[{reduce_overflow_error, <<"Reduce output must shrink", _/binary>>}]}, Result),
?assert(meck:called(couch_log, error, '_')).
js_reduce_should_return_object_on_log(_) ->
config:set("query_server_config", "reduce_limit", "log", false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"javascript">>, [sum_js()], KVs),
?assertMatch([<<"result">>, [_ | _]], Result),
?assert(meck:called(couch_log, info, '_')).
js_reduce_should_return_object_on_false(_) ->
config:set("query_server_config", "reduce_limit", "false", false),
meck:reset(couch_log),
KVs = gen_sum_kvs(),
{ok, [Result]} = couch_query_servers:reduce(<<"javascript">>, [sum_js()], KVs),
?assertMatch([<<"result">>, [_ | _]], Result),
?assertNot(meck:called(couch_log, info, '_')).
should_split_large_batches(_) ->
Req = {json_req, {[]}},
Db = <<"somedb">>,
DDoc = #doc{
id = <<"_design/foo">>,
revs = {0, [<<"bork bork bork">>]},
body =
{[
{<<"filters">>,
{[
{<<"bar">>, <<"function(req, doc) {return true;}">>}
]}}
]}
},
FName = <<"bar">>,
Docs = [
#doc{id = <<"a">>, body = {[]}},
#doc{id = <<"b">>, body = {[]}}
],
meck:new(couch_os_process, [passthrough]),
meck:expect(couch_os_process, prompt, fun(Pid, Data) ->
case Data of
[<<"ddoc">>, _, [<<"filters">>, <<"bar">>], [[_, _], _]] ->
throw({os_process_error, {exit_status, 1}});
[<<"ddoc">>, _, [<<"filters">>, <<"bar">>], [[_], _]] ->
[true, [split_batch]];
_ ->
meck:passthrough([Pid, Data])
end
end),
{ok, Ret} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
?assertEqual([split_batch, split_batch], Ret).
gen_sum_kvs() ->
lists:map(
fun(I) ->
Props = lists:map(
fun(_) ->
K = couch_util:encodeBase64Url(crypto:strong_rand_bytes(16)),
{K, 1}
end,
lists:seq(1, 20)
),
[I, {Props}]
end,
lists:seq(1, 10)
).
sum_js() ->
% Don't do this in real views
<<
"\n"
" function(keys, vals, rereduce) {\n"
" return ['result', vals.concat(vals)]\n"
" }\n"
" "
>>.