%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
%%% Purpose: Test NT specific utilities
-module(nt_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
-export([all/0, suite/0,
init_per_testcase/2, end_per_testcase/2,
nt/1,handle_eventlog/2,
middleman/1,service_basic/1, service_env/1, user_env/1, synced/1,
service_prio/1,
logout/1, debug/1, restart/1, restart_always/1,stopaction/1,
shutdown_io/0,do_shutdown_io/0]).
-define(TEST_SERVICES, [1,2,3,4,5,6,7,8,9,10,11]).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {minutes, 3}}].
all() ->
case {os:type(), os:version()} of
{{win32, nt}, Vsn} when Vsn =< {6,1,999999} ->
[nt, service_basic, service_env, user_env, synced,
service_prio, logout, debug, restart, restart_always,
stopaction];
_ -> [nt]
end.
init_per_testcase(_Func, Config) ->
Config.
end_per_testcase(_Func, _Config) ->
lists:foreach(fun(X) ->
catch remove_service("test_service_" ++ integer_to_list(X))
end, ?TEST_SERVICES),
ok.
erlsrv() ->
"\"" ++ os:find_executable(erlsrv) ++ "\"".
recv_prog_output(Port) ->
receive
{Port, {data, {eol,Data}}} ->
%%io:format("Got data: ~s~n", [Data]),
[ Data | recv_prog_output(Port)];
_X ->
%%io:format("Got data: ~p~n", [_X]),
Port ! {self(), close},
receive
_ ->
[]
end
end.
%%% X == parameters to erlsrv
%%% returns command output without stderr
do_command(X) ->
%%io:format("Command: ~s~n", [erlsrv() ++ " " ++ X]),
Port = open_port({spawn, erlsrv() ++ " " ++ X}, [stream, {line, 100}, eof, in]),
Res = recv_prog_output(Port),
case Res of
[] ->
failed;
_Y ->
%%io:format("~p~n",[_Y]),
ok
end.
create_service(Name) ->
ok = do_command("add " ++ Name).
start_service(Name) ->
ok = do_command("start " ++ Name).
stop_service(Name) ->
ok = do_command("stop " ++ Name).
remove_service(Name) ->
ok = do_command("remove " ++ Name).
do_wait_for_it(_,0) ->
false;
do_wait_for_it(FullName,N) ->
case net_adm:ping(FullName) of
pong ->
true;
_ ->
receive
after 1000 ->
do_wait_for_it(FullName,N-1)
end
end.
wait_for_node(Name) ->
FullName = make_full_name(Name),
do_wait_for_it(FullName,30).
make_full_name(Name) ->
[_,Suffix] = string:tokens(atom_to_list(node()),"@"),
list_to_atom(Name ++ "@" ++ Suffix).
%%% The following tests are only run on NT:
%% Check some basic (cosmetic) service parameters
service_basic(Config) when is_list(Config) ->
Name = "test_service_20",
IntName = Name++"_internal",
Service = [{servicename,Name},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]},
{internalservicename,IntName},
{comment,"Epic comment"}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
S2 = erlsrv:get_service(Name),
{value,{comment,"Epic comment"}} = lists:keysearch(comment,1,S2),
{value,{internalservicename,IntName}} =
lists:keysearch(internalservicename,1,S2),
S3 = lists:keyreplace(comment,1,S2,{comment,"Basic comment"}),
S4 = lists:keyreplace(internalservicename,1,S3,
{internalservicename,"WillNotHappen"}),
ok = erlsrv:store_service(S4),
S5 = erlsrv:get_service(Name),
{value,{comment,"Basic comment"}} = lists:keysearch(comment,1,S5),
{value,{internalservicename,IntName}} =
lists:keysearch(internalservicename,1,S5),
NewName = "test_service_21",
S6 = erlsrv:new_service(NewName,S5,[]), % should remove
% internalservicename
ok = erlsrv:store_service(S6),
S7 = erlsrv:get_service(NewName),
{value,{comment,"Basic comment"}} = lists:keysearch(comment,1,S7),
{value,{internalservicename,[$t,$e,$s,$t | _]}} =
lists:keysearch(internalservicename,1,S7),
remove_service(Name),
remove_service(NewName),
ok.
%% Check that service name and executable is in the environment of the
%% erlang process created by erlsrv.
service_env(Config) when is_list(Config) ->
Name = "test_service_2",
Service = [{servicename,Name},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
Name = rpc:call(make_full_name(Name),os,getenv,
["ERLSRV_SERVICE_NAME"]),
"erlsrv.exe" = filename:basename(
hd(
string:tokens(
rpc:call(make_full_name(Name),
os,
getenv,
["ERLSRV_EXECUTABLE"]),
"\""))),
remove_service(Name),
ok.
%% Check that the user defined environment is ADDED to the service's
%% normal dito.
user_env(Config) when is_list(Config) ->
Name = "test_service_3",
Service = [{servicename,Name},{env,[{"HUBBA","BUBBA"}]},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
true = rpc:call(make_full_name(Name),os,getenv,
["SystemDrive"]) =/= false,
"BUBBA" = rpc:call(make_full_name(Name),os,getenv,["HUBBA"]),
remove_service(Name),
ok.
%% Check that services are stopped and started syncronous and that
%% failed stopactions kill the erlang machine anyway.
synced(Config) when is_list(Config) ->
Name0 = "test_service_4",
Service0 = [{servicename,Name0},
{machine, "N:\\nickeNyfikenPaSjukhus"}],
ok = erlsrv:store_service(Service0),
true = (catch start_service(Name0)) =/= ok,
remove_service(Name0),
Name = "test_service_5",
Service = [{servicename,Name},
{stopaction,"erlang:info(garbage_collection)."},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
T1 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time()),
stop_service(Name),
Diff1 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time()) - T1,
true = Diff1 > 30,
start_service(Name),
true = wait_for_node(Name),
T2 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time()),
remove_service(Name),
Diff2 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time()) - T2,
true = Diff2 > 30,
ok.
%% Check that a service with higher prio create port programs with
%% higher prio.
service_prio(Config) when is_list(Config) ->
Name = "test_service_6",
Service = [{servicename,Name},{prio,"high"},
{env, [{"HEART_COMMAND","echo off"}]},
{args, ["-setcookie", atom_to_list(erlang:get_cookie()),
"-heart"]}],
ok = erlsrv:store_service(Service),
{ok, OldProcs} = get_current_procs(Config),
start_service(Name),
{ok, NewProcs} = get_current_procs(Config),
timer:sleep(2000),
{ok, NewProcs2} = get_current_procs(Config),
remove_service(Name),
Diff = arrived_procs(OldProcs,NewProcs),
io:format("NewProcs ~p~n after sleep~n ~p~n",[Diff, arrived_procs(OldProcs,NewProcs2)]),
%% Not really correct, could fail if another heart is
%% started at the same time...
{value, {"heart.exe",_,"high"}} = lists:keysearch("heart.exe",1,Diff),
ok.
%% Check that logout does not kill services
logout(Config) when is_list(Config) ->
{comment, "Have to be run manually by registering a service with " ++
"heart, logout and log in again and then examine that the heart " ++
"process id is not changed."}.
%% Check the debug options to erlsrv.
debug(Config) when is_list(Config) ->
Name0 = "test_service_7",
%% We used to set the privdir as temporary directory, but for some
%% reason we don't seem to have write access to that directory,
%% so we'll use the directory specified in the next line.
TempDir = "C:/TEMP",
Service0 = [{servicename,Name0},
{workdir,filename:nativename(TempDir)},
{debugtype,"reuse"},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service0),
T1 = calendar:datetime_to_gregorian_seconds(
calendar:local_time()),
%% sleep a little
receive after 2000 -> ok end,
start_service(Name0),
true = wait_for_node(Name0),
LF = filename:join(TempDir, Name0++".debug"),
{ok,Info0} = file:read_file_info(LF),
T2 = calendar:datetime_to_gregorian_seconds(
Info0#file_info.mtime),
true = T2 > T1,
remove_service(Name0),
file:delete(LF),
Name1 = "test_service_8",
Service1 = [{servicename,Name1},
{workdir, filename:nativename(TempDir)},
{debugtype,"new"},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service1),
T3 = calendar:datetime_to_gregorian_seconds(
calendar:local_time()),
%% sleep a little
receive after 2000 -> ok end,
NF = next_logfile(TempDir, Name1),
start_service(Name1),
true = wait_for_node(Name1),
{ok,Info1} = file:read_file_info(NF),
T4 = calendar:datetime_to_gregorian_seconds(
Info1#file_info.mtime),
true = T4 > T3,
remove_service(Name1),
file:delete(NF),
ok.
%% Check the restart options to erlsrv
restart(Config) when is_list(Config) ->
Name = "test_service_9",
Service = [{servicename,Name},
{workdir, filename:nativename(logdir(Config))},
{onfail,"restart"},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
receive after 20000 -> ok end,
rpc:call(make_full_name(Name),erlang,halt,[]),
receive after 1000 -> ok end,
true = wait_for_node(Name),
rpc:call(make_full_name(Name),erlang,halt,[]),
receive after 1000 -> ok end,
false = wait_for_node(Name),
remove_service(Name),
ok.
%% Check the restart options to erlsrv
restart_always(Config) when is_list(Config) ->
Name = "test_service_10",
Service = [{servicename,Name},
{workdir, filename:nativename(logdir(Config))},
{onfail,"restart_always"},
{args, ["-setcookie", atom_to_list(erlang:get_cookie())]}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
rpc:call(make_full_name(Name),erlang,halt,[]),
receive after 1000 -> ok end,
true = wait_for_node(Name),
rpc:call(make_full_name(Name),erlang,halt,[]),
receive after 1000 -> ok end,
true = wait_for_node(Name),
remove_service(Name),
ok.
%% Check that stopaction does not hang output while shutting down
stopaction(Config) when is_list(Config) ->
Name = "test_service_11",
%% Icky, I prepend the first element in the codepath, cause
%% I "suppose" it's the one to where I am.
Service = [{servicename,Name},
{stopaction,atom_to_list(?MODULE) ++ ":shutdown_io()."},
{args, ["-setcookie", atom_to_list(erlang:get_cookie()),
"-pa", hd(code:get_path())]}],
ok = erlsrv:store_service(Service),
start_service(Name),
true = wait_for_node(Name),
T1 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time()),
stop_service(Name),
Diff1 = calendar:datetime_to_gregorian_seconds(
calendar:universal_time()) - T1,
true = Diff1 < 30,
remove_service(Name),
ok.
%%% This test is run on all platforms, but just gives a comment on
%%% other platforms than NT.
nt(Config) when is_list(Config) ->
case {os:type(), os:version()} of
{{win32, nt}, Vsn} when Vsn =< {6,1,999999} ->
nt_run();
{{win32, nt}, _} ->
{skipped, "This test case requires admin privileges on Win 8 and later."};
_ ->
{skipped, "This test case is intended for Win NT only."}
end.
nt_run() ->
start_all(),
create_service("test_service_1"),
R = start_look_for_single("System","ErlSrv","Informational",
".*test_service_1.*started.*"),
start_service("test_service_1"),
Res = look_for_single(R),
io:format("Result from eventlog: ~p~n",
[Res]),
remove_service("test_service_1"),
stop_all(),
ok.
start_all() ->
Pid1 = spawn_link(?MODULE,middleman,[[]]),
register(?MODULE,Pid1),
_Pid2 = nteventlog:start("log_testing",
{?MODULE,handle_eventlog,[Pid1]}).
stop_all() ->
?MODULE ! stop,
nteventlog:stop().
start_look_for_single(Cat,Fac,Sev,MessRE) ->
Ref = make_ref(),
?MODULE ! {lookfor, {self(), Ref, {Cat,Fac,Sev,MessRE}}},
Ref.
look_for_single(Ref) ->
receive
{Ref,Time,Mes} ->
{Time,Mes}
after 60000 ->
timeout
end.
%%% Mes = {Time,Category,Facility,Severity,Message}
handle_eventlog(Mes,Pid) ->
Pid ! Mes.
%%% Waitfor = [{Pid, Ref, {Category,Facility,Severity,MessageRE}} ...]
middleman(Waitfor) ->
receive
{Time,Category,Facility,Severity,Message} ->
io:format("Middleman got ~s...", [Message]),
case match_event({Time,Category,Facility,Severity,Message},
Waitfor) of
{ok, {Pid,Ref,Time,Mes}, Rest} ->
io:format("matched~n"),
Pid ! {Ref,Time,Mes},
middleman(Rest);
_ ->
io:format("no match~n"),
middleman(Waitfor)
end;
{lookfor, X} ->
io:format("Middleman told to look for ~p~n", [X]),
middleman([X|Waitfor]);
stop ->
stopped;
_ ->
middleman(Waitfor)
end.
%%% Matches events, not tail recursive.
match_event(_X, []) ->
nomatch;
match_event({Time,Cat,Fac,Sev,Mes},[{Pid,Ref,{Cat,Fac,Sev,MesRE}} | Tail]) ->
case re:run(Mes,MesRE,[{capture,none}]) of
match ->
%%io:format("Match!~n"),
{ok,{Pid,Ref,Time,Mes},Tail};
nomatch ->
%%io:format("No match~n"),
case match_event({Time,Cat,Fac,Sev,Mes},Tail) of
{ok,X,Rest} ->
{ok,X,[{Pid,Ref,{Cat,Fac,Sev,MesRE}} | Rest]};
X ->
X
end
end;
match_event(X,[Y | T]) ->
%%io:format("X == ~p, Y == ~p~n",[X,Y]),
case match_event(X,T) of
{ok,Z,R} ->
{ok,Z,[Y|R]};
XX ->
XX
end.
arrived_procs(_,[]) ->
[];
arrived_procs(OldProcs,[{Executable, Pid, Priority} | TNewProcs]) ->
case lists:keysearch(Pid,2,OldProcs) of
{value, _} ->
arrived_procs(OldProcs, TNewProcs);
false ->
[{Executable, Pid, Priority} | arrived_procs(OldProcs, TNewProcs)]
end.
get_current_procs(Config) ->
P = open_port({spawn,nt_info(Config) ++ " -E"},
[{line,10000}]),
L = receive
{P,{data,{eol,D}}} ->
D;
_ -> "error. "
end,
P ! {self(), close},
receive
{P, closed} -> ok
end,
{done,{ok,Tok,_},_} = erl_scan:tokens([],L,0),
erl_parse:parse_term(Tok).
nt_info(Config) when is_list(Config) ->
"\"" ++ filename:join(proplists:get_value(data_dir, Config), "nt_info") ++ "\"".
logdir(Config) ->
proplists:get_value(priv_dir, Config).
look_for_next(Template,L,N) ->
FN = Template ++ integer_to_list(N),
case lists:member(FN,L) of
true ->
look_for_next(Template,L,N+1);
false ->
FN
end.
next_logfile(LD, Servicename) ->
{ok, Files} = file:list_dir(LD),
Ftmpl = Servicename ++ ".debug.",
filename:join(LD,look_for_next(Ftmpl,Files,1)).
%%% Functions run by the service
do_shutdown_io() ->
receive
after 2000 ->
io:format("IO in shutting down...~n"),
erlang:halt()
end.
shutdown_io() ->
spawn(?MODULE,do_shutdown_io,[]).