blob: c6c5fec2c67abffd93b1e8d93d6ecacddc4f9262 [file] [log] [blame]
% 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(config_tests).
-beahiour(config_listener).
-export([handle_config_change/5, handle_config_terminate/3]).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(SHORT_TIMEOUT, 100).
-define(TIMEOUT, 1000).
-define(CONFIG_FIXTURESDIR,
filename:join([?BUILDDIR(), "src", "config", "test", "fixtures"])).
-define(CONFIG_FIXTURE_1,
filename:join([?CONFIG_FIXTURESDIR, "config_tests_1.ini"])).
-define(CONFIG_FIXTURE_2,
filename:join([?CONFIG_FIXTURESDIR, "config_tests_2.ini"])).
-define(CONFIG_FIXTURE_TEMP,
begin
FileName = filename:join([?TEMPDIR, "config_temp.ini"]),
{ok, Fd} = file:open(FileName, write),
ok = file:truncate(Fd),
ok = file:close(Fd),
FileName
end).
-define(DEPS, [couch_stats, couch_log, folsom, lager,
goldrush, syntax_tools, compiler, config]).
setup() ->
setup(?CONFIG_CHAIN).
setup({temporary, Chain}) ->
setup(Chain);
setup({persistent, Chain}) ->
setup(lists:append(Chain, [?CONFIG_FIXTURE_TEMP]));
setup(Chain) ->
ok = application:set_env(config, ini_files, Chain),
test_util:start_applications(?DEPS).
setup_empty() ->
setup([]).
setup_config_listener() ->
setup(),
{ok, Pid} = spawn_listener(),
config:listen_for_changes(?MODULE, {Pid, self(), []}),
{Pid, self()}.
teardown({Pid, _}) ->
stop_listener(Pid),
[application:stop(App) || App <- ?DEPS];
teardown(_) ->
[application:stop(App) || App <- ?DEPS].
teardown(_, _) ->
[application:stop(App) || App <- ?DEPS].
handle_config_change("remove_handler", _Key, _Value, _Persist, _State) ->
remove_handler;
handle_config_change("update_state", Key, _Value, _Persist, {Listener, Subscriber, Items}) ->
NewState = {Listener, Subscriber, [Key|Items]},
ok = reply(NewState, NewState),
{ok, NewState};
handle_config_change(Section, Key, Value, Persist, State) ->
ok = reply({{Section, Key, Value, Persist}, State}, State),
{ok, State}.
handle_config_terminate(Self, Reason, State) ->
ok = reply({stop, Self, Reason, State}, State),
ok.
reply(Reply, {Listener, _, _}) ->
call_sync(Listener, {set, Reply}).
wait_reply(Listener) ->
call_sync(Listener, get).
config_test_() ->
{
"CouchDB config tests",
[
config_get_tests(),
config_set_tests(),
config_del_tests(),
config_override_tests(),
config_persistent_changes_tests(),
config_no_files_tests(),
config_listener_behaviour_tests(),
config_unicode_tests()
]
}.
config_get_tests() ->
{
"Config get tests",
{
foreach,
fun setup/0, fun teardown/1,
[
should_load_all_configs(),
should_locate_daemons_section(),
should_locate_mrview_handler(),
should_return_undefined_atom_on_missed_section(),
should_return_undefined_atom_on_missed_option(),
should_return_custom_default_value_on_missed_option(),
should_only_return_default_on_missed_option(),
should_fail_to_get_non_string_value(),
should_return_any_supported_default()
]
}
}.
config_set_tests() ->
{
"Config set tests",
{
foreach,
fun setup/0, fun teardown/1,
[
should_update_option(),
should_create_new_section(),
should_fail_to_set_non_string_value()
]
}
}.
config_del_tests() ->
{
"Config deletion tests",
{
foreach,
fun setup/0, fun teardown/1,
[
should_return_undefined_atom_after_option_deletion(),
should_be_ok_on_deleting_unknown_options(),
should_fail_to_delete_non_string_value()
]
}
}.
config_override_tests() ->
{
"Configs overide tests",
{
foreachx,
fun setup/1, fun teardown/2,
[
{{temporary, [?CONFIG_DEFAULT]},
fun should_ensure_in_defaults/2},
{{temporary, [?CONFIG_DEFAULT, ?CONFIG_FIXTURE_1]},
fun should_override_options/2},
{{temporary, [?CONFIG_DEFAULT, ?CONFIG_FIXTURE_2]},
fun should_create_new_sections_on_override/2},
{{temporary, [?CONFIG_DEFAULT, ?CONFIG_FIXTURE_1,
?CONFIG_FIXTURE_2]},
fun should_win_last_in_chain/2}
]
}
}.
config_persistent_changes_tests() ->
{
"Config persistent changes",
{
foreachx,
fun setup/1, fun teardown/2,
[
{{persistent, [?CONFIG_DEFAULT]},
fun should_write_changes/2},
{{temporary, [?CONFIG_DEFAULT]},
fun should_ensure_that_default_wasnt_modified/2},
{{temporary, [?CONFIG_FIXTURE_TEMP]},
fun should_ensure_that_written_to_last_config_in_chain/2}
]
}
}.
config_no_files_tests() ->
{
"Test config with no files",
{
foreach,
fun setup_empty/0, fun teardown/1,
[
should_ensure_that_no_ini_files_loaded(),
should_create_non_persistent_option(),
should_create_persistent_option()
]
}
}.
config_listener_behaviour_tests() ->
{
"Test config_listener behaviour",
{
foreach,
fun setup_config_listener/0, fun teardown/1,
[
fun should_handle_value_change/1,
fun should_pass_correct_state_to_handle_config_change/1,
fun should_pass_correct_state_to_handle_config_terminate/1,
fun should_pass_subscriber_pid_to_handle_config_terminate/1,
fun should_not_call_handle_config_after_related_process_death/1,
fun should_remove_handler_when_requested/1
]
}
}.
config_unicode_tests() ->
{
"Config unicode tests",
{
foreachx,
fun setup/1, fun teardown/2,
[
{{temporary, [?CONFIG_DEFAULT, ?CONFIG_FIXTURE_2]},
fun should_get_non_latin1_value/2},
{{persistent, [?CONFIG_DEFAULT, ?CONFIG_FIXTURE_2]},
fun should_set_non_latin1_value/2},
{{persistent, [?CONFIG_DEFAULT, ?CONFIG_FIXTURE_2]},
fun should_delete_non_latin1_value/2}
]
}
}.
should_load_all_configs() ->
?_assert(length(config:all()) > 0).
should_locate_daemons_section() ->
?_assert(length(config:get("daemons")) > 0).
should_locate_mrview_handler() ->
?_assertEqual("{couch_mrview_http, handle_view_req}",
config:get("httpd_design_handlers", "_view")).
should_return_undefined_atom_on_missed_section() ->
?_assertEqual(undefined,
config:get("foo", "bar")).
should_return_undefined_atom_on_missed_option() ->
?_assertEqual(undefined,
config:get("httpd", "foo")).
should_return_custom_default_value_on_missed_option() ->
?_assertEqual("bar",
config:get("httpd", "foo", "bar")).
should_only_return_default_on_missed_option() ->
?_assertEqual("0",
config:get("httpd", "port", "bar")).
should_fail_to_get_non_string_value() ->
?_assertException(error, badarg,
config:get(<<"foo">>, <<"bar">>, <<"baz">>)),
?_assertException(error, badarg,
config:get([f, o, o], [b, a, r], [b, a, z])).
should_get_non_latin1_value(_, _) ->
?_test(begin
?assertEqual("true",
config:get("fizbang", [1090, 1077, 1089, 1090]))
end).
should_return_any_supported_default() ->
Values = [undefined, "list", true, false, 0.1, 1],
Tests = [{lists:flatten(io_lib:format("for type(~p)", [V])), V}
|| V <- Values],
[{T, ?_assertEqual(V, config:get(<<"foo">>, <<"bar">>, V))}
|| {T, V} <- Tests].
should_update_option() ->
?_assertEqual("severe",
begin
ok = config:set("log", "level", "severe", false),
config:get("log", "level")
end).
should_create_new_section() ->
?_assertEqual("bang",
begin
undefined = config:get("new_section", "bizzle"),
ok = config:set("new_section", "bizzle", "bang", false),
config:get("new_section", "bizzle")
end).
should_fail_to_set_non_string_value() ->
?_assertException(error, badarg,
config:set(<<"foo">>, <<"bar">>, <<"baz">>, false)),
?_assertException(error, badarg,
config:set([f, o, o], [b, a, r], [b, a, z], false)).
should_set_non_latin1_value(_, _) ->
?_test(begin
?assertEqual(ok,
config:set("fizbang", [1090, 1077, 1089, 1090], "false")),
?assertEqual("false",
config:get("fizbang", [1090, 1077, 1089, 1090]))
end).
should_return_undefined_atom_after_option_deletion() ->
?_assertEqual(undefined,
begin
ok = config:delete("log", "level", false),
config:get("log", "level")
end).
should_be_ok_on_deleting_unknown_options() ->
?_assertEqual(ok, config:delete("zoo", "boo", false)).
should_fail_to_delete_non_string_value() ->
?_assertException(error, badarg,
config:delete(<<"foo">>, <<"bar">>, false)),
?_assertException(error, badarg,
config:delete([f, o, o], [b, a, r], false)).
should_delete_non_latin1_value(_, _) ->
?_test(begin
?assertEqual(ok,
config:delete("fizbang", [1090, 1077, 1089, 1090]))
end).
should_ensure_in_defaults(_, _) ->
?_test(begin
?assertEqual("500",
config:get("couchdb", "max_dbs_open")),
?assertEqual("5986",
config:get("httpd", "port")),
?assertEqual(undefined,
config:get("fizbang", "unicode"))
end).
should_override_options(_, _) ->
?_test(begin
?assertEqual("10",
config:get("couchdb", "max_dbs_open")),
?assertEqual("4895",
config:get("httpd", "port"))
end).
should_create_new_sections_on_override(_, _) ->
?_test(begin
?assertEqual("80",
config:get("httpd", "port")),
?assertEqual("normalized",
config:get("fizbang", "unicode"))
end).
should_win_last_in_chain(_, _) ->
?_assertEqual("80", config:get("httpd", "port")).
should_write_changes(_, _) ->
?_test(begin
?assertEqual("5986",
config:get("httpd", "port")),
?assertEqual(ok,
config:set("httpd", "port", "8080")),
?assertEqual("8080",
config:get("httpd", "port")),
?assertEqual(ok,
config:delete("httpd", "bind_address", "8080")),
?assertEqual(undefined,
config:get("httpd", "bind_address"))
end).
should_ensure_that_default_wasnt_modified(_, _) ->
?_test(begin
?assertEqual("5986",
config:get("httpd", "port")),
?assertEqual("127.0.0.1",
config:get("httpd", "bind_address"))
end).
should_ensure_that_written_to_last_config_in_chain(_, _) ->
?_test(begin
?assertEqual("8080",
config:get("httpd", "port")),
?assertEqual(undefined,
config:get("httpd", "bind_address"))
end).
should_ensure_that_no_ini_files_loaded() ->
?_assertEqual(0, length(config:all())).
should_create_non_persistent_option() ->
?_assertEqual("80",
begin
ok = config:set("httpd", "port", "80", false),
config:get("httpd", "port")
end).
should_create_persistent_option() ->
?_assertEqual("127.0.0.1",
begin
ok = config:set("httpd", "bind_address", "127.0.0.1"),
config:get("httpd", "bind_address")
end).
should_handle_value_change({Pid, _}) ->
?_test(begin
ok = config:set("httpd", "port", "80", false),
?assertMatch({{"httpd", "port", "80", false}, _}, wait_reply(Pid))
end).
should_pass_correct_state_to_handle_config_change({Pid, _}) ->
?_test(begin
ok = config:set("httpd", "port", "80", false),
?assertMatch({_, {Pid, _, []}}, wait_reply(Pid)),
ok = config:set("update_state", "foo", "any", false),
?assertMatch({Pid, _, ["foo"]}, wait_reply(Pid))
end).
should_pass_correct_state_to_handle_config_terminate({Pid, _}) ->
?_test(begin
%% prepare some state
ok = config:set("httpd", "port", "80", false),
?assertMatch({_, {Pid, _, []}}, wait_reply(Pid)),
ok = config:set("update_state", "foo", "any", false),
?assertMatch({Pid, _, ["foo"]}, wait_reply(Pid)),
%% remove handler
ok = config:set("remove_handler", "any", "any", false),
Reply = wait_reply(Pid),
?assertMatch({stop, _, remove_handler, _}, Reply),
{stop, Subscriber, _Reason, State} = Reply,
?assert(is_pid(Subscriber)),
?assertMatch({Pid, Subscriber, ["foo"]}, State)
end).
should_pass_subscriber_pid_to_handle_config_terminate({Pid, SubscriberPid}) ->
?_test(begin
ok = config:set("remove_handler", "any", "any", false),
Reply = wait_reply(Pid),
?assertMatch({stop, _, remove_handler, _}, Reply),
{stop, Subscriber, _Reason, _State} = Reply,
?assertMatch(SubscriberPid, Subscriber)
end).
should_not_call_handle_config_after_related_process_death({Pid, _}) ->
?_test(begin
ok = config:set("remove_handler", "any", "any", false),
Reply = wait_reply(Pid),
?assertMatch({stop, _, remove_handler, _}, Reply),
ok = config:set("httpd", "port", "80", false),
?assertMatch(undefined, wait_reply(Pid))
end).
should_remove_handler_when_requested({Pid, _}) ->
?_test(begin
?assertEqual(1, n_handlers()),
ok = config:set("remove_handler", "any", "any", false),
Reply = wait_reply(Pid),
?assertMatch({stop, _, remove_handler, _}, Reply),
?assertEqual(0, n_handlers())
end).
call_sync(Listener, Msg) ->
Ref = make_ref(),
Listener ! {Ref, self(), Msg},
receive
{ok, Ref, Reply} -> Reply
after ?TIMEOUT ->
throw({error, {timeout, call_sync}})
end.
spawn_listener() ->
{ok, spawn(fun() -> loop(undefined) end)}.
stop_listener(Listener) ->
call_sync(Listener, stop).
loop(State) ->
receive
{Ref, From, stop} ->
From ! {ok, Ref, ok},
ok;
{Ref, From, {set, Value}} ->
From ! {ok, Ref, ok},
loop(Value);
{Ref, From, get} ->
From ! {ok, Ref, State},
loop(undefined)
end.
n_handlers() ->
length(gen_event:which_handlers(config_event)).