blob: 38532f2f7db34382a717cefb640e144c57c3b4d8 [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).
%% tests are UNIX-specific, will not function under Windows
-ifdef(WINDOWS).
-undef(TEST).
-define(NOTEST, 1).
-endif.
-include_lib("couch/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(TRIES, 20).
-define(TRY_DELAY_MS, 100).
-define(TIMEOUT, 1000).
setup(DName) ->
Ctx = test_util:start(?MODULE, [couch_log], [{dont_mock, [config]}]),
{ok, OsDPid} = couch_os_daemons:start_link(),
config:set("os_daemons", DName,
filename:join([?FIXTURESDIR, DName]), false),
% Set configuration option to be used by configuration_reader_test_
% This will be used in os_daemon_configer.escript:test_get_cfg2
config:set("uuids", "algorithm","sequential", false),
ensure_n_daemons_are_alive(1),
{Ctx, OsDPid}.
teardown(_, {Ctx, OsDPid}) ->
test_util:stop_sync_throw(OsDPid, fun() ->
exit(OsDPid, shutdown)
end, {timeout, os_daemon_stop}, ?TIMEOUT),
test_util:stop(Ctx).
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}]
}
}.
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
config:delete("os_daemons", DName, false),
{ok, Tab2} = couch_os_daemons:info(),
?_assertEqual([], ets:tab2list(Tab2))
end).
should_spawn_multiple_daemons(DName, _) ->
?_test(begin
config:set("os_daemons", "bar",
filename:join([?FIXTURESDIR, DName]), false),
config:set("os_daemons", "baz",
filename:join([?FIXTURESDIR, DName]), false),
ensure_n_daemons_are_alive(3), % DName, "bar" and "baz"
{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
config:set("os_daemons", "bar",
filename:join([?FIXTURESDIR, DName]), false),
ensure_n_daemons_are_alive(2), % DName and "bar"
{ok, Daemons} = couch_os_daemons:info([table]),
lists:foreach(fun(D) ->
check_daemon(D)
end, Daemons),
config:delete("os_daemons", "bar", false),
ensure_n_daemons_are_alive(1), % Dname only, "bar" should be dead
{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, 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, 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),
config:delete("os_daemons", DName, false).
check_daemon(D) ->
check_daemon(D, D#daemon.name).
check_daemon(D, Name) ->
check_daemon(D, Name, 0).
check_daemon(D, Name, Errs) ->
?assert(is_port(D#daemon.port)),
?assertEqual(Name, D#daemon.name),
?assertNotEqual(undefined, D#daemon.kill),
?assertEqual(running, D#daemon.status),
?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).
daemons() ->
{ok, Daemons} = couch_os_daemons:info([table]),
Daemons.
ensure_n_daemons_are_alive(NumDaemons) ->
retry(fun() -> length(daemons()) == NumDaemons end, "spawning"),
retry(fun() ->
lists:all(fun(D) -> D#daemon.kill =/= undefined end, daemons())
end, "waiting for kill flag").
retry(Pred, FailReason) ->
retry(Pred, ?TRIES, FailReason).
retry(_Pred, 0, FailReason) ->
erlang:error({assertion_failed,[{module, ?MODULE}, {line, ?LINE},
{reason, "Timed out: " ++ FailReason}]});
retry(Pred, N, FailReason) ->
case Pred() of
true ->
ok;
false ->
timer:sleep(?TRY_DELAY_MS),
retry(Pred, N - 1, FailReason)
end.