blob: 66bbbb5ec3a1d4990dbfff3243ea8575a72e3dde [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(couchdb_os_daemons_tests).
-include("couch_eunit.hrl").
%% keep in sync with couchdb/couch_os_daemons.erl
-record(daemon, {
port,
name,
cmd,
kill,
status=running,
cfg_patterns=[],
errors=[],
buf=[]
}).
-define(DAEMON_CONFIGER, "os_daemon_configer.escript").
-define(DAEMON_LOOPER, "os_daemon_looper.escript").
-define(DAEMON_BAD_PERM, "os_daemon_bad_perm.sh").
-define(DAEMON_CAN_REBOOT, "os_daemon_can_reboot.sh").
-define(DAEMON_DIE_ON_BOOT, "os_daemon_die_on_boot.sh").
-define(DAEMON_DIE_QUICKLY, "os_daemon_die_quickly.sh").
-define(DAEMON_CFGREG, "test_cfg_register").
-define(DELAY, 100).
-define(FIXTURES_BUILDDIR,
filename:join([?BUILDDIR, "test", "couchdb", "fixtures"])).
-define(TIMEOUT, 1000).
setup(DName) ->
{ok, CfgPid} = couch_config:start_link(?CONFIG_CHAIN),
{ok, OsDPid} = couch_os_daemons:start_link(),
Path = case DName of
?DAEMON_CONFIGER ->
filename:join([?FIXTURES_BUILDDIR, DName]);
?DAEMON_CFGREG ->
filename:join([?FIXTURES_BUILDDIR, DName]);
_ ->
filename:join([?FIXTURESDIR, DName])
end,
couch_config:set("os_daemons", DName, Path, false),
timer:sleep(?DELAY), % sleep a bit to let daemon set kill flag
{CfgPid, OsDPid}.
teardown(_, {CfgPid, OsDPid}) ->
erlang:monitor(process, CfgPid),
couch_config:stop(),
receive
{'DOWN', _, _, CfgPid, _} ->
ok
after ?TIMEOUT ->
throw({timeout, config_stop})
end,
erlang:monitor(process, OsDPid),
exit(OsDPid, normal),
receive
{'DOWN', _, _, OsDPid, _} ->
ok
after ?TIMEOUT ->
throw({timeout, os_daemon_stop})
end.
os_daemons_test_() ->
{
"OS Daemons tests",
{
foreachx,
fun setup/1, fun teardown/2,
[{?DAEMON_LOOPER, Fun} || Fun <- [
fun should_check_daemon/2,
fun should_check_daemon_table_form/2,
fun should_clean_tables_on_daemon_remove/2,
fun should_spawn_multiple_daemons/2,
fun should_keep_alive_one_daemon_on_killing_other/2
]]
}
}.
configuration_reader_test_() ->
{
"OS Daemon requests CouchDB configuration",
{
foreachx,
fun setup/1, fun teardown/2,
[{?DAEMON_CONFIGER,
fun should_read_write_config_settings_by_daemon/2}]
}
}.
error_test_() ->
{
"OS Daemon process error tests",
{
foreachx,
fun setup/1, fun teardown/2,
[{?DAEMON_BAD_PERM, fun should_fail_due_to_lack_of_permissions/2},
{?DAEMON_DIE_ON_BOOT, fun should_die_on_boot/2},
{?DAEMON_DIE_QUICKLY, fun should_die_quickly/2},
{?DAEMON_CAN_REBOOT, fun should_not_being_halted/2}]
}
}.
configuration_register_test_() ->
{
"OS daemon subscribed to config changes",
{
foreachx,
fun setup/1, fun teardown/2,
[{?DAEMON_CFGREG, Fun} || Fun <- [
fun should_start_daemon/2,
fun should_restart_daemon_on_section_change/2,
fun should_not_restart_daemon_on_changing_ignored_section_key/2,
fun should_restart_daemon_on_section_key_change/2
]]
}
}.
should_check_daemon(DName, _) ->
?_test(begin
{ok, [D]} = couch_os_daemons:info([table]),
check_daemon(D, DName)
end).
should_check_daemon_table_form(DName, _) ->
?_test(begin
{ok, Tab} = couch_os_daemons:info(),
[D] = ets:tab2list(Tab),
check_daemon(D, DName)
end).
should_clean_tables_on_daemon_remove(DName, _) ->
?_test(begin
couch_config:delete("os_daemons", DName, false),
{ok, Tab2} = couch_os_daemons:info(),
?_assertEqual([], ets:tab2list(Tab2))
end).
should_spawn_multiple_daemons(DName, _) ->
?_test(begin
couch_config:set("os_daemons", "bar",
filename:join([?FIXTURESDIR, DName]), false),
couch_config:set("os_daemons", "baz",
filename:join([?FIXTURESDIR, DName]), false),
timer:sleep(?DELAY),
{ok, Daemons} = couch_os_daemons:info([table]),
lists:foreach(fun(D) ->
check_daemon(D)
end, Daemons),
{ok, Tab} = couch_os_daemons:info(),
lists:foreach(fun(D) ->
check_daemon(D)
end, ets:tab2list(Tab))
end).
should_keep_alive_one_daemon_on_killing_other(DName, _) ->
?_test(begin
couch_config:set("os_daemons", "bar",
filename:join([?FIXTURESDIR, DName]), false),
timer:sleep(?DELAY),
{ok, Daemons} = couch_os_daemons:info([table]),
lists:foreach(fun(D) ->
check_daemon(D)
end, Daemons),
couch_config:delete("os_daemons", "bar", false),
timer:sleep(?DELAY),
{ok, [D2]} = couch_os_daemons:info([table]),
check_daemon(D2, DName),
{ok, Tab} = couch_os_daemons:info(),
[T] = ets:tab2list(Tab),
check_daemon(T, DName)
end).
should_read_write_config_settings_by_daemon(DName, _) ->
?_test(begin
% have to wait till daemon run all his tests
% see daemon's script for more info
timer:sleep(?TIMEOUT),
{ok, [D]} = couch_os_daemons:info([table]),
check_daemon(D, DName)
end).
should_fail_due_to_lack_of_permissions(DName, _) ->
?_test(should_halts(DName, 1000)).
should_die_on_boot(DName, _) ->
?_test(should_halts(DName, 1000)).
should_die_quickly(DName, _) ->
?_test(should_halts(DName, 4000)).
should_not_being_halted(DName, _) ->
?_test(begin
timer:sleep(1000),
{ok, [D1]} = couch_os_daemons:info([table]),
check_daemon(D1, DName, running, 0),
% Should reboot every two seconds. We're at 1s, so wait
% until 3s to be in the middle of the next invocation's
% life span.
timer:sleep(2000),
{ok, [D2]} = couch_os_daemons:info([table]),
check_daemon(D2, DName, running, 1),
% If the kill command changed, that means we rebooted the process.
?assertNotEqual(D1#daemon.kill, D2#daemon.kill)
end).
should_halts(DName, Time) ->
timer:sleep(Time),
{ok, [D]} = couch_os_daemons:info([table]),
check_dead(D, DName),
couch_config:delete("os_daemons", DName, false).
should_start_daemon(DName, _) ->
?_test(begin
wait_for_start(10),
{ok, [D]} = couch_os_daemons:info([table]),
check_daemon(D, DName, running, 0, [{"s1"}, {"s2", "k"}])
end).
should_restart_daemon_on_section_change(DName, _) ->
?_test(begin
wait_for_start(10),
{ok, [D1]} = couch_os_daemons:info([table]),
couch_config:set("s1", "k", "foo", false),
wait_for_restart(10),
{ok, [D2]} = couch_os_daemons:info([table]),
check_daemon(D2, DName, running, 0, [{"s1"}, {"s2", "k"}]),
?assertNotEqual(D1, D2)
end).
should_not_restart_daemon_on_changing_ignored_section_key(_, _) ->
?_test(begin
wait_for_start(10),
{ok, [D1]} = couch_os_daemons:info([table]),
couch_config:set("s2", "k2", "baz", false),
timer:sleep(?DELAY),
{ok, [D2]} = couch_os_daemons:info([table]),
?assertEqual(D1, D2)
end).
should_restart_daemon_on_section_key_change(DName, _) ->
?_test(begin
wait_for_start(10),
{ok, [D1]} = couch_os_daemons:info([table]),
couch_config:set("s2", "k", "bingo", false),
wait_for_restart(10),
{ok, [D2]} = couch_os_daemons:info([table]),
check_daemon(D2, DName, running, 0, [{"s1"}, {"s2", "k"}]),
?assertNotEqual(D1, D2)
end).
wait_for_start(0) ->
erlang:error({assertion_failed,
[{module, ?MODULE},
{line, ?LINE},
{reason, "Timeout on waiting daemon for start"}]});
wait_for_start(N) ->
case couch_os_daemons:info([table]) of
{ok, []} ->
timer:sleep(?DELAY),
wait_for_start(N - 1);
_ ->
timer:sleep(?TIMEOUT)
end.
wait_for_restart(0) ->
erlang:error({assertion_failed,
[{module, ?MODULE},
{line, ?LINE},
{reason, "Timeout on waiting daemon for restart"}]});
wait_for_restart(N) ->
{ok, [D]} = couch_os_daemons:info([table]),
case D#daemon.status of
restarting ->
timer:sleep(?DELAY),
wait_for_restart(N - 1);
_ ->
timer:sleep(?TIMEOUT)
end.
check_daemon(D) ->
check_daemon(D, D#daemon.name).
check_daemon(D, Name) ->
check_daemon(D, Name, running).
check_daemon(D, Name, Status) ->
check_daemon(D, Name, Status, 0).
check_daemon(D, Name, Status, Errs) ->
check_daemon(D, Name, Status, Errs, []).
check_daemon(D, Name, Status, Errs, CfgPatterns) ->
?assert(is_port(D#daemon.port)),
?assertEqual(Name, D#daemon.name),
?assertNotEqual(undefined, D#daemon.kill),
?assertEqual(Status, D#daemon.status),
?assertEqual(CfgPatterns, D#daemon.cfg_patterns),
?assertEqual(Errs, length(D#daemon.errors)),
?assertEqual([], D#daemon.buf).
check_dead(D, Name) ->
?assert(is_port(D#daemon.port)),
?assertEqual(Name, D#daemon.name),
?assertNotEqual(undefined, D#daemon.kill),
?assertEqual(halted, D#daemon.status),
?assertEqual(nil, D#daemon.errors),
?assertEqual(nil, D#daemon.buf).