%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 92 -*-
%%% Copyright 2012 Erlware, LLC. All Rights Reserved.
%%%
%%% This file is provided to you 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(rlx_extended_bin_SUITE).
-export([suite/0,
init_per_suite/1,
end_per_suite/1,
init_per_testcase/2,
all/0,
ping/1,
shortname_ping/1,
attach/1,
pid/1,
restart/1,
reboot/1,
escript/1,
remote_console/1,
replace_os_vars/1,
replace_os_vars_custom_location/1,
replace_os_vars_dev_mode/1,
replace_os_vars_twice/1,
custom_start_script_hooks/1,
builtin_wait_for_vm_start_script_hook/1,
builtin_pid_start_script_hook/1,
builtin_wait_for_process_start_script_hook/1,
mixed_custom_and_builtin_start_script_hooks/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/file.hrl").
suite() ->
[{timetrap,{seconds,30}}].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
ok.
init_per_testcase(_, Config) ->
DataDir = filename:join(proplists:get_value(priv_dir, Config), ?MODULE),
LibDir1 = filename:join([DataDir, rlx_test_utils:create_random_name("lib_dir1_")]),
ok = rlx_util:mkdir_p(LibDir1),
State = rlx_state:new([], [{lib_dirs, [LibDir1]}], [release]),
{ok, State1} = rlx_config:do(State),
[{lib1, LibDir1},
{state, State1} | Config].
all() ->
[ping, shortname_ping, attach, pid, restart, reboot, escript,
remote_console, replace_os_vars, replace_os_vars_dev_mode, replace_os_vars_twice,
custom_start_script_hooks, builtin_wait_for_vm_start_script_hook,
builtin_pid_start_script_hook, builtin_wait_for_process_start_script_hook,
mixed_custom_and_builtin_start_script_hooks].
ping(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% now start/stop the release to make sure the extended script is working
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
%% a ping should fail after stopping a node
{error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
shortname_ping(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
VmArgs = filename:join([LibDir1, "vm.args"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{vm_args, VmArgs},
{generate_start_script, true},
{extended_start_script, true}
]),
ec_file:write(VmArgs, "-sname foo\n\n"
"-setcookie cookie\n"),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% now start/stop the release to make sure the extended script is working
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
%% a ping should fail after stopping a node
{error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
attach(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% now start/stop the release to make sure the extended script is working
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo attach", "&"])),
timer:sleep(2000),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
{error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
pid(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% check for a valid pid
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _Pid} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
{error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
restart(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% a restart is a gracious operation that does not involve the
%% death of the VM, so the pid should be the same
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, Pid1} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo restart"])),
timer:sleep(2000),
{ok, Pid2} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
{error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
?assertEqual(Pid1, Pid2).
reboot(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% a reboot involves stopping the emulator, it needs to be restarted
%% though
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, Pid1} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo reboot"])),
timer:sleep(2000),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, Pid2} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
{error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
?assertNotEqual(Pid1, Pid2).
escript(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
ok = ec_file:write(filename:join([OutputDir, "foo", "script.erl"]),
[rlx_test_utils:escript_contents()]),
%% now start/stop the release to make sure the extended script is working
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
[ExpectedOutput] = io_lib:format("~s",
[filename:join([OutputDir, "foo"])]),
{ok, Output} = sh(filename:join([OutputDir, "foo", "bin", "foo escript script.erl"])),
?assertEqual(ExpectedOutput, Output).
remote_console(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
%% now start/stop the release to make sure the extended script is working
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo remote_console &"])),
timer:sleep(2000),
{ok, NodesStr} = sh(filename:join([OutputDir, "foo", "bin", "foo eval 'nodes(connected).'"])),
Nodes = rlx_test_utils:list_to_term(NodesStr),
?assertEqual(1, length(Nodes)),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])).
replace_os_vars(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
SysConfig = filename:join([LibDir1, "sys.config"]),
VmArgs = filename:join([LibDir1, "vm.args"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{sys_config, SysConfig},
{vm_args, VmArgs},
{generate_start_script, true},
{extended_start_script, true}
]),
rlx_test_utils:write_config(SysConfig,
[[{goal_app, [{var1, "${VAR1}"}]}]]),
ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
"-setcookie ${COOKIE}\n"),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"},
{"VAR1", "v1"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, Node1} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
"sys." ++
rlx_test_utils:unescape_string(Node1) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% start the node again but this time with different env variables to replace
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"},
{"VAR1", "v2"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, Node2} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
"sys." ++
rlx_test_utils:unescape_string(Node2) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
ok.
replace_os_vars_custom_location(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
SysConfig = filename:join([LibDir1, "sys.config"]),
VmArgs = filename:join([LibDir1, "vm.args"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{sys_config, SysConfig},
{vm_args, VmArgs},
{generate_start_script, true},
{extended_start_script, true}
]),
rlx_test_utils:write_config(SysConfig,
[[{goal_app, [{var1, "${VAR1}"}]}]]),
ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
"-setcookie ${COOKIE}\n"),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"},
{"VAR1", "v1"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, Node1} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join(["/", "tmp",
"sys." ++
rlx_test_utils:unescape_string(Node1) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% start the node again but this time with different env variables to replace
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"},
{"VAR1", "v2"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, Node2} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join(["/", "tmp",
"sys." ++
rlx_test_utils:unescape_string(Node2) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"RELX_OUT_FILE_PATH", "/tmp"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
ok.
replace_os_vars_twice(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
SysConfig = filename:join([LibDir1, "sys.config"]),
VmArgs = filename:join([LibDir1, "vm.args"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{sys_config, SysConfig},
{vm_args, VmArgs},
{generate_start_script, true},
{extended_start_script, true}
]),
rlx_test_utils:write_config(SysConfig,
[[{goal_app, [{var1, "${VAR1}"}]}]]),
ec_file:write(VmArgs, "-sname node\n\n"
"-setcookie cookie\n"),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"VAR1", "v1"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"}]),
{ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"}]),
{ok, "\"node\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"}]),
{ok, "cookie"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"}]),
{ok, Node1} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
"sys." ++
rlx_test_utils:unescape_string(Node1) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"}]),
%% start the node again but this time don't replace env variables
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, "\"${VAR1}\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"])),
{ok, "\"node\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"])),
{ok, "cookie"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
ok.
replace_os_vars_dev_mode(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
SysConfig = filename:join([LibDir1, "sys.config"]),
VmArgs = filename:join([LibDir1, "vm.args"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{sys_config, SysConfig},
{vm_args, VmArgs},
{dev_mode, true},
{generate_start_script, true},
{extended_start_script, true}
]),
rlx_test_utils:write_config(SysConfig,
[[{goal_app, [{var1, "${VAR1}"}]}]]),
ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
"-setcookie ${COOKIE}\n"),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do([{relname, foo},
{relvsn, "0.0.1"},
{goals, []},
{lib_dirs, [LibDir1]},
{log_level, 3},
{output_dir, OutputDir},
{config, ConfigFile}], ["release"]),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"},
{"VAR1", "v1"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
{ok, Node1} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
"sys." ++
rlx_test_utils:unescape_string(Node1) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node1"},
{"COOKIE", "cookie1"}]),
%% start the node again but this time with different env variables to replace
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"},
{"VAR1", "v2"}]),
timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'erlang:get_cookie().'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
{ok, Node2} = sh(filename:join([OutputDir, "foo", "bin",
"foo eval 'atom_to_list(node()).'"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
%% check that the replaced files have been created in the right place
?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
"sys." ++
rlx_test_utils:unescape_string(Node2) ++
".config"]))),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
[{"RELX_REPLACE_OS_VARS", "1"},
{"NODENAME", "node2"},
{"COOKIE", "cookie2"}]),
ok.
custom_start_script_hooks(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true},
{extended_start_script_hooks, [
{pre_start, [
{custom, "hooks/pre_start"}
]},
{post_start, [
{custom, "hooks/post_start"}
]},
{pre_stop, [
{custom, "hooks/pre_stop"}
]},
{post_stop, [
{custom, "hooks/post_stop"}
]}
]},
{mkdir, "scripts"},
{overlay, [{copy, "./pre_start", "bin/hooks/pre_start"},
{copy, "./post_start", "bin/hooks/post_start"},
{copy, "./pre_stop", "bin/hooks/pre_stop"},
{copy, "./post_stop", "bin/hooks/post_stop"}]}
]),
%% write the hook scripts, each of them will write an erlang term to a file
%% that will later be consulted
ok = file:write_file(filename:join([LibDir1, "./pre_start"]),
"#!/bin/bash\n# $*\necho \\{pre_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
ok = file:write_file(filename:join([LibDir1, "./post_start"]),
"#!/bin/bash\n# $*\necho \\{post_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
ok = file:write_file(filename:join([LibDir1, "./pre_stop"]),
"#!/bin/bash\n# $*\necho \\{pre_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
ok = file:write_file(filename:join([LibDir1, "./post_stop"]),
"#!/bin/bash\n# $*\necho \\{post_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
OutputDir, ConfigFile),
%% now start/stop the release to make sure the script hooks are really getting
%% executed
os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
timer:sleep(2000),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
%% now check that the output file contains the expected format
{ok,[{pre_start, foo, _, foo},
{post_start, foo, _, foo},
{pre_stop, foo, _, foo},
{post_stop, foo, _, foo}]} = file:consult(filename:join([OutputDir, "foo", "test"])).
builtin_pid_start_script_hook(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true},
{extended_start_script_hooks, [
{post_start, [
{pid, filename:join([OutputDir, "foo.pid"])}
]}
]}
]),
{ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
OutputDir, ConfigFile),
%% now start/stop the release to make sure the script hooks are really getting
%% executed
os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
%% check that the pid file really was created
?assert(ec_file:exists(filename:join([OutputDir, "foo.pid"]))),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
ok.
builtin_wait_for_vm_start_script_hook(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true},
{extended_start_script_hooks, [
{post_start, [wait_for_vm_start]}
]}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
OutputDir, ConfigFile),
%% now start/stop the release to make sure the script hooks are really getting
%% executed
os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
% this run doesn't need the sleep because the wait_for_vm_start
% start script makes it unnecessary
%timer:sleep(2000),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
ok.
builtin_wait_for_process_start_script_hook(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1",
[stdlib,kernel], []),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true},
{extended_start_script_hooks, [
{post_start, [wait_for_vm_start,
{wait_for_process, goal_app_srv_signal}]}
]}
]),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
{ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
OutputDir, ConfigFile),
%% now start/stop the release to make sure the script hooks are really getting
%% executed
%% get the current time, we'll measure how long it took for the node to
%% start, it must be at least 3 seconds which is the time it takes the
%% goal_app_srv to register the signal
T1 = os:timestamp(),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
T2 = timer:now_diff(os:timestamp(), T1),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
?assert((T2 div 1000) > 3000),
ok.
mixed_custom_and_builtin_start_script_hooks(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1",
[stdlib,kernel], []),
OutputDir = filename:join([proplists:get_value(priv_dir, Config),
rlx_test_utils:create_random_name("relx-output")]),
ConfigFile = filename:join([LibDir1, "relx.config"]),
rlx_test_utils:write_config(ConfigFile,
[{release, {foo, "0.0.1"},
[goal_app]},
{lib_dirs, [filename:join(LibDir1, "*")]},
{generate_start_script, true},
{extended_start_script, true},
{extended_start_script_hooks, [
{pre_start, [
{custom, "hooks/pre_start"}
]},
{post_start, [
wait_for_vm_start,
{pid, filename:join([OutputDir, "foo.pid"])},
{wait_for_process, goal_app_srv_signal},
{custom, "hooks/post_start"}
]},
{pre_stop, [
{custom, "hooks/pre_stop"}
]},
{post_stop, [
{custom, "hooks/post_stop"}
]}
]},
{mkdir, "scripts"},
{overlay, [{copy, "./pre_start", "bin/hooks/pre_start"},
{copy, "./post_start", "bin/hooks/post_start"},
{copy, "./pre_stop", "bin/hooks/pre_stop"},
{copy, "./post_stop", "bin/hooks/post_stop"}]}
]),
%% write the hook scripts, each of them will write an erlang term to a file
%% that will later be consulted
ok = file:write_file(filename:join([LibDir1, "./pre_start"]),
"#!/bin/bash\n# $*\necho \\{pre_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
ok = file:write_file(filename:join([LibDir1, "./post_start"]),
"#!/bin/bash\n# $*\necho \\{post_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
ok = file:write_file(filename:join([LibDir1, "./pre_stop"]),
"#!/bin/bash\n# $*\necho \\{pre_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
ok = file:write_file(filename:join([LibDir1, "./post_stop"]),
"#!/bin/bash\n# $*\necho \\{post_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
{ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
OutputDir, ConfigFile),
%% now start/stop the release to make sure the script hooks are really getting
%% executed
%% get the current time, we'll measure how long it took for the node to
%% start, it must be at least 3 seconds which is the time it takes the
%% goal_app_srv to register the signal
T1 = os:timestamp(),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
% this run doesn't need the sleep because the wait_for_vm_start
% start script makes it unnecessary
T2 = timer:now_diff(os:timestamp(), T1),
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
?assert((T2 div 1000) > 3000),
%% check that the pid file really was created
?assert(ec_file:exists(filename:join([OutputDir, "foo.pid"]))),
os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
%% now check that the output file contains the expected format
{ok,[{pre_start, foo, _, foo},
{post_start, foo, _, foo},
{pre_stop, foo, _, foo},
{post_stop, foo, _, foo}]} = file:consult(filename:join([OutputDir, "foo", "test"])).
%%%===================================================================
%%% Helper Functions
%%%===================================================================
sh(Command) ->
sh(Command, []).
sh(Command, Env) ->
sh(Command, Env, get_cwd()).
sh(Command, Env, Dir) ->
Port = open_port({spawn, lists:flatten(Command)},
[{cd, Dir},
{env, Env},
exit_status,
{line, 16384},
use_stdio, stderr_to_stdout]),
case sh_loop(Port) of
{ok, Ret} ->
{ok, Ret};
{error, Rc, Msg} ->
{error, Rc, Msg}
end.
sh_loop(Port) ->
sh_loop(Port, "").
sh_loop(Port, Acc) ->
receive
{Port, {data, {_, Line}}} ->
sh_loop(Port, Acc ++ Line);
{Port, {exit_status, 0}} ->
{ok, Acc};
{Port, {exit_status, Rc}} ->
{error, Rc, Acc}
end.
get_cwd() ->
{ok, Dir} = file:get_cwd(),
Dir.