summaryrefslogtreecommitdiff
path: root/src/couch/test/eunit/couch_index_tests.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couch/test/eunit/couch_index_tests.erl')
-rw-r--r--src/couch/test/eunit/couch_index_tests.erl234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/couch/test/eunit/couch_index_tests.erl b/src/couch/test/eunit/couch_index_tests.erl
new file mode 100644
index 000000000..fab3806d0
--- /dev/null
+++ b/src/couch/test/eunit/couch_index_tests.erl
@@ -0,0 +1,234 @@
+% 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_index_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch_mrview/include/couch_mrview.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-define(TIMEOUT, 1000).
+
+setup() ->
+ DbName = ?tempdb(),
+ {ok, Db} = couch_db:create(DbName, [?ADMIN_CTX]),
+ ok = couch_db:close(Db),
+ create_design_doc(DbName, <<"_design/foo">>, <<"bar">>),
+ tracer_new(),
+ DbName.
+
+teardown(DbName) ->
+ tracer_delete(),
+ couch_server:delete(DbName, [?ADMIN_CTX]).
+
+couch_index_ioq_priority_test_() ->
+ {
+ "Test ioq_priority for views",
+ {
+ setup,
+ fun test_util:start_couch/0, fun test_util:stop_couch/1,
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [
+ fun check_io_priority_for_updater/1,
+ fun check_io_priority_for_compactor/1
+ ]
+ }
+ }
+ }.
+
+
+check_io_priority_for_updater(DbName) ->
+ ?_test(begin
+ {ok, IndexerPid} = couch_index_server:get_index(
+ couch_mrview_index, DbName, <<"_design/foo">>),
+ CouchIndexUpdaterPid = updater_pid(IndexerPid),
+ tracer_record(CouchIndexUpdaterPid),
+
+ create_docs(DbName),
+
+ CommittedSeq = couch_util:with_db(DbName, fun(Db) -> couch_db:get_update_seq(Db) end),
+ couch_index:get_state(IndexerPid, CommittedSeq),
+ [UpdaterPid] = wait_spawn_event_for_pid(CouchIndexUpdaterPid),
+
+ [UpdaterMapProcess] = wait_spawn_by_anonymous_fun(
+ UpdaterPid, '-start_update/4-fun-0-'),
+
+ ?assert(wait_set_io_priority(
+ UpdaterMapProcess, {view_update, DbName, <<"_design/foo">>})),
+
+ [UpdaterWriterProcess] = wait_spawn_by_anonymous_fun(
+ UpdaterPid, '-start_update/4-fun-1-'),
+ ?assert(wait_set_io_priority(
+ UpdaterWriterProcess, {view_update, DbName, <<"_design/foo">>})),
+
+ ok
+ end).
+
+check_io_priority_for_compactor(DbName) ->
+ ?_test(begin
+ {ok, IndexerPid} = couch_index_server:get_index(
+ couch_mrview_index, DbName, <<"_design/foo">>),
+ {ok, CompactorPid} = couch_index:get_compactor_pid(IndexerPid),
+ tracer_record(CompactorPid),
+
+ create_docs(DbName),
+
+ couch_index:compact(IndexerPid),
+ wait_spawn_event_for_pid(CompactorPid),
+
+ [CompactorProcess] = wait_spawn_by_anonymous_fun(
+ CompactorPid, '-handle_call/3-fun-0-'),
+ ?assert(wait_set_io_priority(
+ CompactorProcess, {view_compact, DbName, <<"_design/foo">>})),
+ ok
+ end).
+
+create_docs(DbName) ->
+ {ok, Db} = couch_db:open(DbName, [?ADMIN_CTX]),
+ Doc1 = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"doc1">>},
+ {<<"value">>, 1}
+
+ ]}),
+ Doc2 = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"doc2">>},
+ {<<"value">>, 2}
+
+ ]}),
+ Doc3 = couch_doc:from_json_obj({[
+ {<<"_id">>, <<"doc3">>},
+ {<<"value">>, 3}
+
+ ]}),
+ {ok, _} = couch_db:update_docs(Db, [Doc1, Doc2, Doc3]),
+ couch_db:ensure_full_commit(Db),
+ couch_db:close(Db).
+
+create_design_doc(DbName, DDName, ViewName) ->
+ {ok, Db} = couch_db:open(DbName, [?ADMIN_CTX]),
+ DDoc = couch_doc:from_json_obj({[
+ {<<"_id">>, DDName},
+ {<<"language">>, <<"javascript">>},
+ {<<"views">>, {[
+ {ViewName, {[
+ {<<"map">>, <<"function(doc) { emit(doc.value, null); }">>}
+ ]}}
+ ]}}
+ ]}),
+ {ok, Rev} = couch_db:update_doc(Db, DDoc, []),
+ couch_db:ensure_full_commit(Db),
+ couch_db:close(Db),
+ Rev.
+
+wait_set_io_priority(Pid, IOPriority) ->
+ test_util:wait_value(fun() ->
+ does_process_set_io_priority(Pid, IOPriority)
+ end, true).
+
+does_process_set_io_priority(Pid, IOPriority) ->
+ PutCallsArgs = find_calls_to_fun(Pid, {erlang, put, 2}),
+ lists:any(fun([_, Priority]) -> Priority =:= IOPriority end, PutCallsArgs).
+
+wait_events(MatchSpec) ->
+ test_util:wait_other_value(fun() -> select(MatchSpec) end, []).
+
+find_spawned_by_anonymous_fun(ParentPid, Name) ->
+ AnonymousFuns = select(ets:fun2ms(fun
+ ({spawned, Pid, _TS, _Name, _Dict, [PPid, {erlang, apply, [Fun, _]}]})
+ when is_function(Fun) andalso PPid =:= ParentPid -> {Pid, Fun}
+ end)),
+ lists:filtermap(fun({Pid, Fun}) ->
+ case erlang:fun_info(Fun, name) of
+ {name, Name} -> {true, Pid};
+ _ -> false
+ end
+ end, AnonymousFuns).
+
+find_calls_to_fun(Pid, {Module, Function, Arity}) ->
+ select(ets:fun2ms(fun
+ ({call, P, _TS, _Name, _Dict, [{M, F, Args}]})
+ when length(Args) =:= Arity
+ andalso M =:= Module
+ andalso F =:= Function
+ andalso P =:= Pid
+ -> Args
+ end)).
+
+wait_spawn_event_for_pid(ParentPid) ->
+ wait_events(ets:fun2ms(fun
+ ({spawned, Pid, _TS, _Name, _Dict, [P, _]}) when P =:= ParentPid -> Pid
+ end)).
+
+wait_spawn_by_anonymous_fun(ParentPid, Name) ->
+ test_util:wait_other_value(fun() ->
+ find_spawned_by_anonymous_fun(ParentPid, Name)
+ end, []).
+
+updater_pid(IndexerPid) ->
+ {links, Links} = process_info(IndexerPid, links),
+ [Pid] = select_process_by_name_prefix(Links, "couch_index_updater:init/1"),
+ Pid.
+
+select_process_by_name_prefix(Pids, Name) ->
+ lists:filter(fun(Pid) ->
+ Key = couch_debug:process_name(Pid),
+ string:str(Key, Name) =:= 1
+ end, Pids).
+
+select(MatchSpec) ->
+ lists:filtermap(fun(Event) ->
+ case ets:test_ms(Event, MatchSpec) of
+ {ok, false} -> false;
+ {ok, Result} -> {true, Result};
+ _ -> false
+ end
+ end, tracer_events()).
+
+
+%% ========================
+%% Tracer related functions
+%% ------------------------
+tracer_new() ->
+ ets:new(?MODULE, [public, named_table]),
+ {ok, _Tracer} = dbg:tracer(process, {fun tracer_collector/2, 0}),
+ ok.
+
+tracer_delete() ->
+ dbg:stop_clear(),
+ (catch ets:delete(?MODULE)),
+ ok.
+
+tracer_record(Pid) ->
+ {ok, _} = dbg:tp(erlang, put, x),
+ {ok, _} = dbg:p(Pid, [c, p, sos]),
+ ok.
+
+tracer_events() ->
+ Events = [{Idx, E} || [Idx, E] <- ets:match(?MODULE, {{trace, '$1'}, '$2'})],
+ {_, Sorted} = lists:unzip(lists:keysort(1, Events)),
+ Sorted.
+
+tracer_collector(Msg, Seq) ->
+ ets:insert(?MODULE, {{trace, Seq}, normalize_trace_msg(Msg)}),
+ Seq + 1.
+
+normalize_trace_msg(TraceMsg) ->
+ case tuple_to_list(TraceMsg) of
+ [trace_ts, Pid, Type | Info] ->
+ {TraceInfo, [Timestamp]} = lists:split(length(Info)-1, Info),
+ {Type, Pid, Timestamp, couch_debug:process_name(Pid), process_info(Pid), TraceInfo};
+ [trace, Pid, Type | TraceInfo] ->
+ {Type, Pid, os:timestamp(), couch_debug:process_name(Pid), process_info(Pid), TraceInfo}
+ end.