%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2009-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% -module(reltool_server_SUITE). -export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, init_per_suite/1, end_per_suite/1, init_per_testcase/2, end_per_testcase/2]). -compile(export_all). -include_lib("reltool/src/reltool.hrl"). -include("reltool_test_lib.hrl"). -include_lib("common_test/include/ct.hrl"). -define(NODE_NAME, '__RELTOOL__TEMPORARY_TEST__NODE__'). -define(WORK_DIR, "reltool_work_dir"). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Initialization functions. init_per_suite(Config) -> ?ignore(file:make_dir(?WORK_DIR)), reltool_test_lib:init_per_suite(Config). end_per_suite(Config) -> reltool_test_lib:end_per_suite(Config). init_per_testcase(Func,Config) -> reltool_test_lib:init_per_testcase(Func,Config). end_per_testcase(Func,Config) -> reltool_test_lib:end_per_testcase(Func,Config). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% SUITE specification suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [start_server, set_config, get_config, create_release, create_script, create_target, create_embedded, create_standalone, create_old_target, otp_9135, otp_9229_exclude_app, otp_9229_exclude_mod, get_apps, set_app_and_undo, set_apps_and_undo, load_config_and_undo, reset_config_and_undo, save_config, dependencies]. groups() -> []. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %% The test cases %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Start a server process and check that it does not crash start_server(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); start_server(_Config) -> {ok, Pid} = ?msym({ok, _}, reltool:start_server([])), Libs = lists:sort(erl_libs()), StrippedDefault = case Libs of [] -> {sys, []}; _ -> {sys, [{lib_dirs, Libs}]} end, ?m({ok, StrippedDefault}, reltool:get_config(Pid)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Start a server process and check that it does not crash set_config(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); set_config(_Config) -> Libs = lists:sort(erl_libs()), Default = {sys, [ {mod_cond, all}, {incl_cond, derived}, {root_dir, code:root_dir()}, {lib_dirs, Libs} ]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Default}])), StrippedDefault = case Libs of [] -> {sys, []}; _ -> {sys, [{lib_dirs, Libs}]} end, ?m({ok, StrippedDefault}, reltool:get_config(Pid)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Check that get_config returns the expected derivates and defaults %% as specified get_config(_Config) -> Sys = {sys,[{incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), ?m({ok, Sys}, reltool:get_config(Pid)), ?m({ok, Sys}, reltool:get_config(Pid,false,false)), ?msym({ok,{sys,[{incl_cond, exclude}, {erts,[]}, {app,kernel,[{incl_cond,include},{mod,_,[]}|_]}, {app,sasl,[{incl_cond,include},{mod,_,[]}|_]}, {app,stdlib,[{incl_cond,include},{mod,_,[]}|_]}]}}, reltool:get_config(Pid,false,true)), ?msym({ok,{sys,[{root_dir,_}, {lib_dirs,_}, {mod_cond,all}, {incl_cond,exclude}, {app,kernel,[{incl_cond,include},{vsn,undefined}]}, {app,sasl,[{incl_cond,include},{vsn,undefined}]}, {app,stdlib,[{incl_cond,include},{vsn,undefined}]}, {boot_rel,"start_clean"}, {rel,"start_clean","1.0",[]}, {rel,"start_sasl","1.0",[sasl]}, {emu_name,"beam"}, {relocatable,true}, {profile,development}, {incl_sys_filters,[".*"]}, {excl_sys_filters,[]}, {incl_app_filters,[".*"]}, {excl_app_filters,[]}, {incl_archive_filters,[".*"]}, {excl_archive_filters,["^include$","^priv$"]}, {archive_opts,[]}, {rel_app_type,permanent}, {app_file,keep}, {debug_info,keep}]}}, reltool:get_config(Pid,true,false)), KVsn = latest(kernel), StdVsn = latest(stdlib), ?msym({ok,{sys,[{root_dir,_}, {lib_dirs,_}, {mod_cond,all}, {incl_cond,exclude}, {erts,[]}, {app,kernel,[{incl_cond,include},{vsn,KVsn},{mod,_,[]}|_]}, {app,sasl,[{incl_cond,include},{vsn,_},{mod,_,[]}|_]}, {app,stdlib,[{incl_cond,include},{vsn,StdVsn},{mod,_,[]}|_]}, {boot_rel,"start_clean"}, {rel,"start_clean","1.0",[]}, {rel,"start_sasl","1.0",[sasl]}, {emu_name,"beam"}, {relocatable,true}, {profile,development}, {incl_sys_filters,[".*"]}, {excl_sys_filters,[]}, {incl_app_filters,[".*"]}, {excl_app_filters,[]}, {incl_archive_filters,[".*"]}, {excl_archive_filters,["^include$","^priv$"]}, {archive_opts,[]}, {rel_app_type,permanent}, {app_file,keep}, {debug_info,keep}]}}, reltool:get_config(Pid,true,true)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% OTP-9135, test that app_file option can be set to all | keep | strip otp_9135(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); otp_9135(_Config) -> Libs = lists:sort(erl_libs()), StrippedDefaultSys = case Libs of [] -> []; _ -> {lib_dirs, Libs} end, Config1 = {sys,[{app_file, keep}]}, % this is the default {ok, Pid1} = ?msym({ok, _}, reltool:start_server([{config, Config1}])), ?m({ok, {sys,StrippedDefaultSys}}, reltool:get_config(Pid1)), ?m(ok, reltool:stop(Pid1)), Config2 = {sys,[{app_file, strip}]}, {ok, Pid2} = ?msym({ok, _}, reltool:start_server([{config, Config2}])), ExpectedConfig2 = StrippedDefaultSys++[{app_file,strip}], ?m({ok, {sys,ExpectedConfig2}}, reltool:get_config(Pid2)), ?m(ok, reltool:stop(Pid2)), Config3 = {sys,[{app_file, all}]}, {ok, Pid3} = ?msym({ok, _}, reltool:start_server([{config, Config3}])), ExpectedConfig3 = StrippedDefaultSys++[{app_file,all}], ?m({ok, {sys,ExpectedConfig3}}, reltool:get_config(Pid3)), ?m(ok, reltool:stop(Pid3)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate releases create_release(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); create_release(_Config) -> %% Configure the server RelName = "Just testing...", RelVsn = "1.0", Config = {sys, [ {lib_dirs, []}, {boot_rel, RelName}, {rel, RelName, RelVsn, [kernel, stdlib]} ]}, %% Generate release ErtsVsn = erlang:system_info(version), Apps = application:loaded_applications(), {value, {_, _, KernelVsn}} = lists:keysearch(kernel, 1, Apps), {value, {_, _, StdlibVsn}} = lists:keysearch(stdlib, 1, Apps), Rel = {release, {RelName, RelVsn}, {erts, ErtsVsn}, [{kernel, KernelVsn}, {stdlib, StdlibVsn}]}, ?m({ok, Rel}, reltool:get_rel([{config, Config}], RelName)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate boot scripts create_script(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); create_script(_Config) -> %% Configure the server RelName = "Just testing", RelVsn = "1.0", Config = {sys, [ {lib_dirs, []}, {boot_rel, RelName}, {rel, RelName, RelVsn, [stdlib, kernel]} ]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Config}])), %% Generate release file ErtsVsn = erlang:system_info(version), Apps = application:loaded_applications(), {value, {_, _, KernelVsn}} = lists:keysearch(kernel, 1, Apps), {value, {_, _, StdlibVsn}} = lists:keysearch(stdlib, 1, Apps), Rel = {release, {RelName, RelVsn}, {erts, ErtsVsn}, [{kernel, KernelVsn}, {stdlib, StdlibVsn}]}, ?m({ok, Rel}, reltool:get_rel(Pid, RelName)), ?m(ok, file:write_file(filename:join([?WORK_DIR, RelName ++ ".rel"]), io_lib:format("~p.\n", [Rel]))), %% Generate script file {ok, Cwd} = file:get_cwd(), ?m(ok, file:set_cwd(?WORK_DIR)), ?m(ok, systools:make_script(RelName, [])), {ok, [OrigScript]} = ?msym({ok, [_]}, file:consult(RelName ++ ".script")), ?m(ok, file:set_cwd(Cwd)), {ok, Script} = ?msym({ok, _}, reltool:get_script(Pid, RelName)), %% OrigScript2 = sort_script(OrigScript), %% Script2 = sort_script(Script), %% ?m(OrigScript2, Script2), ?m(equal, diff_script(OrigScript, Script)), %% Stop server ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate target system create_target(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); create_target(_Config) -> %% Configure the server RelName1 = "Just testing", RelName2 = "Just testing with SASL", RelVsn = "1.0", Config = {sys, [ {root_dir, code:root_dir()}, {lib_dirs, []}, {boot_rel, RelName2}, {rel, RelName1, RelVsn, [stdlib, kernel]}, {rel, RelName2, RelVsn, [sasl, stdlib, kernel]}, {app, sasl, [{incl_cond, include}]} ]}, %% Generate target file TargetDir = filename:join([?WORK_DIR, "target_development"]), ?m(ok, reltool_utils:recursive_delete(TargetDir)), ?m(ok, file:make_dir(TargetDir)), ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, Config}])]), ok = ?m(ok, reltool:create_target([{config, Config}], TargetDir)), Erl = filename:join([TargetDir, "bin", "erl"]), {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)), ?msym(ok, stop_node(Node)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate embedded target system create_embedded(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); create_embedded(_Config) -> %% Configure the server RelName1 = "Just testing", RelName2 = "Just testing with SASL", RelVsn = "1.0", Config = {sys, [ {lib_dirs, []}, {profile, embedded}, {boot_rel, RelName2}, {rel, RelName1, RelVsn, [stdlib, kernel]}, {rel, RelName2, RelVsn, [sasl, stdlib, kernel]}, {app, sasl, [{incl_cond, include}]} ]}, %% Generate target file TargetDir = filename:join([?WORK_DIR, "target_embedded"]), ?m(ok, reltool_utils:recursive_delete(TargetDir)), ?m(ok, file:make_dir(TargetDir)), ok = ?m(ok, reltool:create_target([{config, Config}], TargetDir)), Erl = filename:join([TargetDir, "bin", "erl"]), {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)), ?msym(ok, stop_node(Node)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate standalone system create_standalone(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); create_standalone(_Config) -> %% Configure the server ExDir = code:lib_dir(reltool, examples), EscriptName = "display_args", Escript = filename:join([ExDir, EscriptName]), Config = {sys, [ {lib_dirs, []}, {escript, Escript, [{incl_cond, include}]}, {profile, standalone} ]}, %% Generate target file TargetDir = filename:join([?WORK_DIR, "target_standalone"]), ?m(ok, reltool_utils:recursive_delete(TargetDir)), ?m(ok, file:make_dir(TargetDir)), ok = ?m(ok, reltool:create_target([{config, Config}], TargetDir)), BinDir = filename:join([TargetDir, "bin"]), Erl = filename:join([BinDir, "erl"]), {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)), RootDir = ?ignore(rpc:call(Node, code, root_dir, [])), ?msym(ok, stop_node(Node)), Expected = iolist_to_binary(["Root dir: ", RootDir, "\n" "Script args: [\"-arg1\",\"arg2\",\"arg3\"]\n", "Smp: false\n", "ExitCode:0"]), io:format("Expected: ~s\n", [Expected]), ?m(Expected, run(BinDir, EscriptName ++ " -arg1 arg2 arg3")), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate old type of target system create_old_target(TestInfo) when is_atom(TestInfo) -> reltool_test_lib:tc_info(TestInfo); create_old_target(_Config) -> ?skip("Old style of target", []), %% Configure the server RelName1 = "Just testing", RelName2 = "Just testing with SASL", RelVsn = "1.0", Config = {sys, [ {lib_dirs, []}, {boot_rel, RelName2}, {rel, RelName1, RelVsn, [stdlib, kernel]}, {rel, RelName2, RelVsn, [sasl, stdlib, kernel]}, {relocatable, false}, % Implies explicit old style installation {app, sasl, [{incl_cond, include}]} ]}, %% Generate target file TargetDir = filename:join([?WORK_DIR, "target_old_style"]), ?m(ok, reltool_utils:recursive_delete(TargetDir)), ?m(ok, file:make_dir(TargetDir)), ok = ?m(ok, reltool:create_target([{config, Config}], TargetDir)), %% io:format("Will fail on Windows (should patch erl.ini)\n", []), ok = ?m(ok, reltool:install(RelName2, TargetDir)), Erl = filename:join([TargetDir, "bin", "erl"]), {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)), ?msym(ok, stop_node(Node)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% OTP-9229 - handle duplicated module names, i.e. same module name %% exists in two applications. %% Include on app, exclude the other otp_9229_exclude_app(Config) -> DataDir = ?config(data_dir,Config), LibDir = filename:join(DataDir,"otp_9229"), %% Configure the server ExclApp = {sys, [ {root_dir, code:root_dir()}, {lib_dirs, [LibDir]}, {incl_cond,exclude}, {app,x,[{incl_cond,include}]}, {app,y,[{incl_cond,exclude}]}, {app,kernel,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]} ]}, %% Generate target file TargetDir = filename:join([?WORK_DIR, "target_dupl_mod_excl_app"]), ?m(ok, reltool_utils:recursive_delete(TargetDir)), ?m(ok, file:make_dir(TargetDir)), ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, ExclApp}])]), ?m({ok,["Module mylib exists in applications x and y. Using module from application x."]}, reltool:get_status([{config, ExclApp}])), ok = ?m(ok, reltool:create_target([{config, ExclApp}], TargetDir)), Erl = filename:join([TargetDir, "bin", "erl"]), {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)), AbsTargetDir = filename:absname(TargetDir), XArchive = "x-1.0.ez", AbsXArchive = filename:join([AbsTargetDir,lib,XArchive]), XEbin = ["ebin","x-1.0",XArchive], YArchive = "y-1.0.ez", AbsYArchive = filename:join([AbsTargetDir,lib,YArchive]), ?m(true, filelib:is_file(AbsXArchive)), ?m(XEbin, mod_path(Node,x)), ?m(XEbin, mod_path(Node,mylib)), ?m(false, filelib:is_file(AbsYArchive)), ?m(non_existing, mod_path(Node,y)), ?msym(ok, stop_node(Node)), ok. %% Include both apps, but exclude common module from one app otp_9229_exclude_mod(Config) -> DataDir = ?config(data_dir,Config), LibDir = filename:join(DataDir,"otp_9229"), %% Configure the server ExclMod = {sys, [ {root_dir, code:root_dir()}, {lib_dirs, [LibDir]}, {incl_cond,exclude}, {app,x,[{incl_cond,include}]}, {app,y,[{incl_cond,include},{mod, mylib,[{incl_cond,exclude}]}]}, {app,kernel,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]} ]}, %% Generate target file TargetDir = filename:join([?WORK_DIR, "target_dupl_mod_excl_mod"]), ?m(ok, reltool_utils:recursive_delete(TargetDir)), ?m(ok, file:make_dir(TargetDir)), ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, ExclMod}])]), ?m({ok,["Module mylib exists in applications x and y. Using module from application x."]}, reltool:get_status([{config, ExclMod}])), ok = ?m(ok, reltool:create_target([{config, ExclMod}], TargetDir)), Erl = filename:join([TargetDir, "bin", "erl"]), {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)), AbsTargetDir = filename:absname(TargetDir), XArchive = "x-1.0.ez", AbsXArchive = filename:join([AbsTargetDir,lib,XArchive]), XEbin = ["ebin","x-1.0",XArchive], YArchive = "y-1.0.ez", AbsYArchive = filename:join([AbsTargetDir,lib,YArchive]), YEbin = ["ebin","y-1.0",YArchive], ?m(true, filelib:is_file(AbsXArchive)), ?m(XEbin, mod_path(Node,x)), ?m(XEbin, mod_path(Node,mylib)), ?m(true, filelib:is_file(AbsYArchive)), ?m(YEbin, mod_path(Node,y)), %% Remove path to XEbin and check that mylib is not located in YEbin Mylib = rpc:call(Node,code,which,[mylib]), rpc:call(Node,code,del_path,[filename:dirname(Mylib)]), ?m(non_existing, mod_path(Node,mylib)), ?msym(ok, stop_node(Node)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test the interface used by the GUI: %% get_app %% get_apps %% set_app %% set_apps %% load_config %% reset_config %% %% Also, for each operation which manipulates the config test %% undo_config - that it is properly undone, and that warnings are %% re-displayed. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% get_apps(_Config) -> Sys = {sys,[{app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,derived}]}, {app,runtime_tools,[{incl_cond,exclude}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), {ok,Sasl} = ?msym({ok,#app{name=sasl}}, reltool_server:get_app(Pid,sasl)), {ok,[#app{name=kernel}, #app{name=sasl}=Sasl, #app{name=stdlib}] = White} = ?msym({ok,_}, reltool_server:get_apps(Pid,whitelist)), {ok,[#app{name=runtime_tools}] = Black} = ?msym({ok,_}, reltool_server:get_apps(Pid,blacklist)), {ok,Derived} = ?msym({ok,_}, reltool_server:get_apps(Pid,derived)), true = lists:keymember(tools,#app.name,Derived), {ok,Source} = ?msym({ok,_}, reltool_server:get_apps(Pid,source)), true = lists:keymember(common_test,#app.name,Source), %% Check that the four lists are disjoint Number = length(White) + length(Black) + length(Derived) + length(Source), WN = lists:usort([N || #app{name=N} <- White]), BN = lists:usort([N || #app{name=N} <- Black]), DN = lists:usort([N || #app{name=N} <- Derived]), SN = lists:usort([N || #app{name=N} <- Source]), AllN = lists:umerge([WN,BN,DN,SN]), ?m(Number,length(AllN)), ?m(ok, reltool:stop(Pid)), ok. set_app_and_undo(Config) -> Sys = {sys,[{lib_dirs,[filename:join(datadir(Config),"faulty_app_file")]}, {incl_cond, exclude}, {app,a,[{incl_cond,include}]}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,include}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), ?m({ok, Sys}, reltool:get_config(Pid)), %% Exclude one module with set_app {ok,Tools} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), Mods = Tools#app.mods, Cover = lists:keyfind(cover,#mod.name,Mods), ExclCover = Cover#mod{incl_cond=exclude}, Tools1 = Tools#app{mods = lists:keyreplace(cover,#mod.name,Mods,ExclCover)}, {ok,ToolsNoCover,[]} = ?msym({ok,_,[]}, reltool_server:set_app(Pid,Tools1)), ?m({ok,ToolsNoCover}, reltool_server:get_app(Pid,tools)), %% Undo ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,Tools}, reltool_server:get_app(Pid,tools)), %%! warning can come twice here... :( ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool:get_status(Pid)), %% Undo again, to check that it toggles ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,ToolsNoCover}, reltool_server:get_app(Pid,tools)), ?m({ok,[]}, reltool:get_status(Pid)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% set_apps_and_undo(Config) -> Sys = {sys,[{lib_dirs,[filename:join(datadir(Config),"faulty_app_file")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,include}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), ?m({ok, Sys}, reltool:get_config(Pid)), %% Exclude one application with set_apps {ok,Tools} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), ExclTools = Tools#app{incl_cond=exclude}, ?m({ok,[]}, reltool_server:set_apps(Pid,[ExclTools])), {ok,NoTools} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), ?m(false, NoTools#app.is_pre_included), ?m(false, NoTools#app.is_included), %% Undo ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,Tools}, reltool_server:get_app(Pid,tools)), %%! warning can come twice here... :( ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool:get_status(Pid)), %% Undo again, to check that it toggles ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,NoTools}, reltool_server:get_app(Pid,tools)), ?m({ok,[]}, reltool:get_status(Pid)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% load_config_and_undo(Config) -> Sys1 = {sys,[{incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,include}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys1}])), ?m({ok, Sys1}, reltool:get_config(Pid)), %% Check that tools is included {ok,Tools1} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), ?m(true, Tools1#app.is_pre_included), ?m(true, Tools1#app.is_included), %% Exclude one application with set_apps Sys2 = {sys,[{lib_dirs,[filename:join(datadir(Config),"faulty_app_file")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,derived}]}, {app,a,[{incl_cond,include}]}]}, ?msym({ok,["a: Cannot parse app file"++_]}, reltool_server:load_config(Pid,Sys2)), %% Check that tools is included (since it is used by sasl) but not %% pre-included (neither included or excluded => undefined) {ok,Tools2} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), ?m(undefined, Tools2#app.is_pre_included), ?m(true, Tools2#app.is_included), %% Undo ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,Tools1}, reltool_server:get_app(Pid,tools)), ?m({ok,[]}, reltool:get_status(Pid)), %% Undo again, to check that it toggles ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,Tools2}, reltool_server:get_app(Pid,tools)), ?msym({ok,["a: Cannot parse app file"++_]}, reltool:get_status(Pid)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% reset_config_and_undo(Config) -> Sys1 = {sys,[{lib_dirs,[filename:join(datadir(Config),"faulty_app_file")]}, {incl_cond, exclude}, {app,a,[{incl_cond,include}]}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,include}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys1}])), ?m({ok, Sys1}, reltool:get_config(Pid)), %% Check that tools is included {ok,Tools1} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), ?m(true, Tools1#app.is_pre_included), ?m(true, Tools1#app.is_included), %% Exclude one application with set_apps Sys2 = {sys,[{incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,exclude}]}]}, ?m({ok,[]}, reltool_server:load_config(Pid,Sys2)), %% Check that tools is excluded {ok,Tools2} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), ?m(false, Tools2#app.is_pre_included), ?m(false, Tools2#app.is_included), %% Reset %%! warning can come twice here... :( ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:reset_config(Pid)), ?m({ok,Tools1}, reltool_server:get_app(Pid,tools)), %% Undo again, to check that it toggles ?m(ok, reltool_server:undo_config(Pid)), ?m({ok,Tools2}, reltool_server:get_app(Pid,tools)), ?m({ok,[]}, reltool:get_status(Pid)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% save_config(Config) -> PrivDir = ?config(priv_dir,Config), Sys = {sys,[{incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), ?m({ok, Sys}, reltool:get_config(Pid)), Simple = filename:join(PrivDir,"save_simple.reltool"), ?m(ok, reltool_server:save_config(Pid,Simple,false,false)), ?m({ok,[Sys]}, file:consult(Simple)), Derivates = filename:join(PrivDir,"save_derivates.reltool"), ?m(ok, reltool_server:save_config(Pid,Derivates,false,true)), ?msym({ok,[{sys,[{incl_cond, exclude}, {erts,[]}, {app,kernel,[{incl_cond,include},{mod,_,[]}|_]}, {app,sasl,[{incl_cond,include},{mod,_,[]}|_]}, {app,stdlib,[{incl_cond,include},{mod,_,[]}|_]}]}]}, file:consult(Derivates)), Defaults = filename:join(PrivDir,"save_defaults.reltool"), ?m(ok, reltool_server:save_config(Pid,Defaults,true,false)), ?msym({ok,[{sys,[{root_dir,_}, {lib_dirs,_}, {mod_cond,all}, {incl_cond,exclude}, {app,kernel,[{incl_cond,include},{vsn,undefined}]}, {app,sasl,[{incl_cond,include},{vsn,undefined}]}, {app,stdlib,[{incl_cond,include},{vsn,undefined}]}, {boot_rel,"start_clean"}, {rel,"start_clean","1.0",[]}, {rel,"start_sasl","1.0",[sasl]}, {emu_name,"beam"}, {relocatable,true}, {profile,development}, {incl_sys_filters,[".*"]}, {excl_sys_filters,[]}, {incl_app_filters,[".*"]}, {excl_app_filters,[]}, {incl_archive_filters,[".*"]}, {excl_archive_filters,["^include$","^priv$"]}, {archive_opts,[]}, {rel_app_type,permanent}, {app_file,keep}, {debug_info,keep}]}]}, file:consult(Defaults)), KVsn = latest(kernel), StdVsn = latest(stdlib), All = filename:join(PrivDir,"save_all.reltool"), ?m(ok, reltool_server:save_config(Pid,All,true,true)), ?msym({ok,[{sys,[{root_dir,_}, {lib_dirs,_}, {mod_cond,all}, {incl_cond,exclude}, {erts,[]}, {app,kernel,[{incl_cond,include},{vsn,KVsn},{mod,_,[]}|_]}, {app,sasl,[{incl_cond,include},{vsn,_},{mod,_,[]}|_]}, {app,stdlib,[{incl_cond,include},{vsn,StdVsn},{mod,_,[]}|_]}, {boot_rel,"start_clean"}, {rel,"start_clean","1.0",[]}, {rel,"start_sasl","1.0",[sasl]}, {emu_name,"beam"}, {relocatable,true}, {profile,development}, {incl_sys_filters,[".*"]}, {excl_sys_filters,[]}, {incl_app_filters,[".*"]}, {excl_app_filters,[]}, {incl_archive_filters,[".*"]}, {excl_archive_filters,["^include$","^priv$"]}, {archive_opts,[]}, {rel_app_type,permanent}, {app_file,keep}, {debug_info,keep}]}]}, file:consult(All)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test calculation of dependencies %% The following test applications are used %% %% x-1.0: x1.erl x2.erl x3.erl %% \ / (x2 calls y1, x3 calls y2) %% y-1.0: y1.erl y2.erl %% \ (y1 calls z1) %% z-1.0 z1.erl %% %% Test includes x and derives y and z. %% dependencies(Config) -> PrivDir = ?config(priv_dir,Config), %% Default: all modules included => y and z are included (derived) Sys = {sys,[{lib_dirs,[filename:join(datadir(Config),"dependencies")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,x,[{incl_cond,include}]}, {app,y,[{incl_cond,derived}]}, {app,z,[{incl_cond,derived}]}]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), ?msym({ok,[#app{name=kernel}, #app{name=sasl}, #app{name=stdlib}, #app{name=x,uses_apps=[y]}]}, reltool_server:get_apps(Pid,whitelist)), {ok, Der} = ?msym({ok,_}, reltool_server:get_apps(Pid,derived)), ?msym([#app{name=y,uses_apps=[z]}, #app{name=z}], rm_missing_app(Der)), ?msym({ok,[]}, reltool_server:get_apps(Pid,source)), %% Excluding x2 => y still included since y2 is used by x3 %% z still included since z1 is used by y1 Sys2 = {sys,[{lib_dirs,[filename:join(datadir(Config),"dependencies")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,x,[{incl_cond,include},{mod,x2,[{incl_cond,exclude}]}]}, {app,y,[{incl_cond,derived}]}, {app,z,[{incl_cond,derived}]}]}, ?m({ok,[]}, reltool_server:load_config(Pid,Sys2)), ?msym({ok,[#app{name=kernel}, #app{name=sasl}, #app{name=stdlib}, #app{name=x,uses_apps=[y]}]}, reltool_server:get_apps(Pid,whitelist)), {ok, Der2} = ?msym({ok,_}, reltool_server:get_apps(Pid,derived)), ?msym([#app{name=y,uses_apps=[z]}, #app{name=z}], rm_missing_app(Der2)), ?msym({ok,[]}, reltool_server:get_apps(Pid,source)), %% Excluding x3 => y still included since y1 is used by x2 %% z still included since z1 is used by y1 Sys3 = {sys,[{lib_dirs,[filename:join(datadir(Config),"dependencies")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,x,[{incl_cond,include},{mod,x3,[{incl_cond,exclude}]}]}, {app,y,[{incl_cond,derived}]}, {app,z,[{incl_cond,derived}]}]}, ?m({ok,[]}, reltool_server:load_config(Pid,Sys3)), ?msym({ok,[#app{name=kernel}, #app{name=sasl}, #app{name=stdlib}, #app{name=x,uses_apps=[y]}]}, reltool_server:get_apps(Pid,whitelist)), {ok, Der3} = ?msym({ok,_}, reltool_server:get_apps(Pid,derived)), ?msym([#app{name=y,uses_apps=[z]}, #app{name=z}], rm_missing_app(Der3)), ?msym({ok,[]}, reltool_server:get_apps(Pid,source)), %% Excluding x2 and x3 => y and z excluded Sys4 = {sys,[{lib_dirs,[filename:join(datadir(Config),"dependencies")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,x,[{incl_cond,include}, {mod,x2,[{incl_cond,exclude}]}, {mod,x3,[{incl_cond,exclude}]}]}, {app,y,[{incl_cond,derived}]}, {app,z,[{incl_cond,derived}]}]}, ?m({ok,[]}, reltool_server:load_config(Pid,Sys4)), ?msym({ok,[#app{name=kernel}, #app{name=sasl}, #app{name=stdlib}, #app{name=x,uses_apps=[]}]}, reltool_server:get_apps(Pid,whitelist)), {ok, Der4} = ?msym({ok,_}, reltool_server:get_apps(Pid,derived)), ?msym([], rm_missing_app(Der4)), ?msym({ok,[#app{name=y}, #app{name=z}]}, reltool_server:get_apps(Pid,source)), %% Excluding y1 => y still included since y2 is used by x3 %% z excluded since not used by any other than y1 Sys5 = {sys,[{lib_dirs,[filename:join(datadir(Config),"dependencies")]}, {incl_cond, exclude}, {app,kernel,[{incl_cond,include}]}, {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,x,[{incl_cond,include}]}, {app,y,[{incl_cond,derived}, {mod,y1,[{incl_cond,exclude}]}]}, {app,z,[{incl_cond,derived}]}]}, ?m({ok,[]}, reltool_server:load_config(Pid,Sys5)), ?msym({ok,[#app{name=kernel}, #app{name=sasl}, #app{name=stdlib}, #app{name=x,uses_apps=[y]}]}, reltool_server:get_apps(Pid,whitelist)), {ok, Der5} = ?msym({ok,_}, reltool_server:get_apps(Pid,derived)), ?msym([#app{name=y,uses_apps=[]}], rm_missing_app(Der5)), ?msym({ok,[#app{name=z}]}, reltool_server:get_apps(Pid,source)), ?m(ok, reltool:stop(Pid)), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Library functions erl_libs() -> case os:getenv("ERL_LIBS") of false -> []; LibStr -> string:tokens(LibStr, ":;") end. datadir(Config) -> %% Removes the trailing slash... filename:nativename(?config(data_dir,Config)). latest(App) -> AppStr = atom_to_list(App), AppDirs = filelib:wildcard(filename:join(code:lib_dir(),AppStr++"-*")), [LatestAppDir|_] = lists:reverse(AppDirs), [_,Vsn] = string:tokens(filename:basename(LatestAppDir),"-"), Vsn. rm_missing_app(Apps) -> lists:keydelete(?MISSING_APP_NAME,#app.name,Apps). diff_script(Script, Script) -> equal; diff_script({script, Rel, Commands1}, {script, Rel, Commands2}) -> diff_cmds(Commands1, Commands2); diff_script({script, Rel1, _}, {script, Rel2, _}) -> {error, {Rel1, Rel2}}. diff_cmds([Cmd | Commands1], [Cmd | Commands2]) -> diff_cmds(Commands1, Commands2); diff_cmds([Cmd1 | _Commands1], [Cmd2 | _Commands2]) -> {diff, {expected, Cmd1}, {actual, Cmd2}}; diff_cmds([], []) -> equal. os_cmd(Cmd) when is_list(Cmd) -> %% Call the plain os:cmd with an echo command appended to print command status %% io:format("os:cmd(~p).\n", [Cmd]), case os:cmd(Cmd++";echo \"#$?\"") of %% There is (as far as I can tell) only one thing that will match this %% and that is too silly to ever be used, but... []-> {99, []}; Return-> %% Find the position of the status code wich is last in the string %% prepended with # case string:rchr(Return, $#) of %% This happens only if the sh command pipe is somehow interrupted 0-> {98, Return}; Position-> Result = string:left(Return,Position - 1), Status = string:substr(Return,Position + 1, length(Return) - Position - 1), {list_to_integer(Status), Result} end end. %% Returns the location (directory) of the given module. Split, %% reverted and relative to the lib dir. mod_path(Node,Mod) -> case rpc:call(Node,code,which,[Mod]) of Path when is_list(Path) -> lists:takewhile( fun("lib") -> false; (_) -> true end, lists:reverse(filename:split(filename:dirname(Path)))); Other -> Other end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Node handling start_node(Name, ErlPath) -> FullName = full_node_name(Name), CmdLine = mk_node_cmdline(Name, ErlPath), io:format("Starting node ~p: ~s~n", [FullName, CmdLine]), case open_port({spawn, CmdLine}, []) of Port when is_port(Port) -> unlink(Port), erlang:port_close(Port), case ping_node(FullName, 50) of ok -> {ok, FullName}; Other -> exit({failed_to_start_node, FullName, Other}) end; Error -> exit({failed_to_start_node, FullName, Error}) end. stop_node(Node) -> monitor_node(Node, true), spawn(Node, fun () -> halt() end), receive {nodedown, Node} -> ok end. mk_node_cmdline(Name) -> Prog = case catch init:get_argument(progname) of {ok,[[P]]} -> P; _ -> exit(no_progname_argument_found) end, mk_node_cmdline(Name, Prog). mk_node_cmdline(Name, Prog) -> Static = "-detached -noinput", Pa = filename:dirname(code:which(?MODULE)), NameSw = case net_kernel:longnames() of false -> "-sname "; true -> "-name "; _ -> exit(not_distributed_node) end, {ok, Pwd} = file:get_cwd(), NameStr = atom_to_list(Name), Prog ++ " " ++ Static ++ " " ++ NameSw ++ " " ++ NameStr ++ " " ++ "-pa " ++ Pa ++ " " ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ NameStr ++ " " ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()). full_node_name(PreName) -> HostSuffix = lists:dropwhile(fun ($@) -> false; (_) -> true end, atom_to_list(node())), list_to_atom(atom_to_list(PreName) ++ HostSuffix). ping_node(_Node, 0) -> {error, net_adm}; ping_node(Node, N) when is_integer(N), N > 0 -> case catch net_adm:ping(Node) of pong -> wait_for_process(Node, code_server, 50); _ -> timer:sleep(1000), ping_node(Node, N-1) end. wait_for_process(_Node, Name, 0) -> {error, Name}; wait_for_process(Node, Name, N) when is_integer(N), N > 0 -> case rpc:call(Node, erlang, whereis, [Name]) of undefined -> timer:sleep(1000), wait_for_process(Node, Name, N-1); {badrpc, _} = Reason -> erlang:error({Reason, Node}); Pid when is_pid(Pid) -> ok end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Run escript run(Dir, Cmd0) -> Cmd = case os:type() of {win32,_} -> filename:nativename(Dir) ++ "\\" ++ Cmd0; _ -> Cmd0 end, do_run(Dir, Cmd). run(Dir, Opts, Cmd0) -> Cmd = case os:type() of {win32,_} -> Opts ++ " " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0; _ -> Opts ++ " " ++ Dir ++ "/" ++ Cmd0 end, do_run(Dir, Cmd). do_run(Dir, Cmd) -> io:format("Run: ~p\n", [Cmd]), Env = [{"PATH",Dir++":"++os:getenv("PATH")}], Port = open_port({spawn,Cmd}, [exit_status,eof,in,{env,Env}]), Res = get_data(Port, []), receive {Port,{exit_status,ExitCode}} -> iolist_to_binary([Res,"ExitCode:"++integer_to_list(ExitCode)]) end. get_data(Port, SoFar) -> receive {Port,{data,Bytes}} -> get_data(Port, [SoFar|Bytes]); {Port,eof} -> erlang:port_close(Port), SoFar end. expected_output([data_dir|T], Data) -> Slash = case os:type() of {win32,_} -> "\\"; _ -> "/" end, [filename:nativename(Data)++Slash|expected_output(T, Data)]; expected_output([H|T], Data) -> [H|expected_output(T, Data)]; expected_output([], _) -> []; expected_output(Bin, _) when is_binary(Bin) -> Bin.