aboutsummaryrefslogblamecommitdiffstats
path: root/test/upgrade_SUITE.erl
blob: 71b1000df8292da806a9993ef9d2acb50642e355 (plain) (tree)
















































































































                                                                                                      

                                                                        


                           


                                                                                               












                                                                                                                 
                                                                               






















































                                                                                        
%% Copyright (c) 2020, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

-module(upgrade_SUITE).
-compile(export_all).
-compile(nowarn_export_all).

-import(ct_helper, [doc/1]).

%% ct.

all() ->
	ct_helper:all(?MODULE).

init_per_suite(Config) ->
	%% Remove environment variables inherited from Erlang.mk.
	os:unsetenv("ERLANG_MK_TMP"),
	os:unsetenv("APPS_DIR"),
	os:unsetenv("DEPS_DIR"),
	os:unsetenv("ERL_LIBS"),
	os:unsetenv("CI_ERLANG_MK"),
	%% Ensure we are using the C locale for all os:cmd calls.
	os:putenv("LC_ALL", "C"),
	Config.

end_per_suite(_Config) ->
	ok.

%% Find GNU Make.

do_find_make_cmd() ->
	case os:getenv("MAKE") of
		false ->
			case os:find_executable("gmake") of
				false -> "make";
				Cmd   -> Cmd
			end;
		Cmd ->
			Cmd
	end.

%% Manipulate the release.

do_get_paths(Example0) ->
	Example = atom_to_list(Example0),
	{ok, CWD} = file:get_cwd(),
	Dir = CWD ++ "/../../examples/" ++ Example,
	Rel = Dir ++ "/_rel/" ++ Example ++ "_example/bin/" ++ Example ++ "_example",
	Log = Dir ++ "/_rel/" ++ Example ++ "_example/log/erlang.log.1",
	{Dir, Rel, Log}.

do_compile_and_start(Example, Config) ->
	Make = do_find_make_cmd(),
	{Dir, Rel, _} = do_get_paths(Example),
	ct:log("~s~n", [os:cmd(Make ++ " -C " ++ Dir ++ " distclean")]),
	%% TERM=dumb disables relx coloring.
	ct:log("~s~n", [os:cmd(Make ++ " -C " ++ Dir ++ " TERM=dumb")]),
	%% @todo Should move the "1" tarball in the correct directory to avoid a warning on downgrade?
	ct:log("~s~n", [os:cmd(Rel ++ " stop")]),
	ct:log("~s~n", [os:cmd(Rel ++ " start")]),
	timer:sleep(2000),
	ct:log("~s~n", [os:cmd(Rel ++ " eval 'application:info()'")]).

do_stop(Example) ->
	{Dir, Rel, Log} = do_get_paths(Example),
	ct:log("~s~n", [os:cmd("sed -i.bak s/\"2\"/\"1\"/ " ++ Dir ++ "/relx.config")]),
	ct:log("~s~n", [os:cmd(Rel ++ " stop")]),
	ct:log("~s~n", [element(2, file:read_file(Log))]).

%% When we are on a tag (git describe --exact-match succeeds),
%% we use the tag before that as a starting point. Otherwise
%% we use the most recent tag.
do_use_ranch_previous(Example) ->
	TagsOutput = os:cmd("git tag | tr - \\~ | sort -V | tr \\~ -"),
	ct:log("~s~n", [TagsOutput]),
	Tags = string:lexemes(TagsOutput, "\n"),
	DescribeOutput = os:cmd("git describe --exact-match"),
	ct:log("~s~n", [DescribeOutput]),
	{CommitOrTag, Prev} = case DescribeOutput of
		"fatal: no tag exactly matches " ++ _ -> {commit, hd(lists:reverse(Tags))};
		_ -> {tag, hd(tl(lists:reverse(Tags)))}
	end,
	do_use_ranch_commit(Example, Prev),
	CommitOrTag.

%% Replace the current Ranch commit with the one given as argument.
do_use_ranch_commit(Example, Commit) ->
	{Dir, _, _} = do_get_paths(Example),
	ct:log("~s~n", [os:cmd(
		"sed -i.bak s/\"dep_ranch_commit = .*\"/\"dep_ranch_commit = "
		++ Commit ++ "\"/ " ++ Dir ++ "/Makefile"
	)]).

%% Remove Ranch and rebuild, this time generating a relup.
do_build_relup(Example, CommitOrTag) ->
	Make = do_find_make_cmd(),
	{Dir, _, _} = do_get_paths(Example),
	ct:log("~s~n", [os:cmd("rm -rf " ++ Dir ++ "/deps/ranch")]),
	ct:log("~s~n", [os:cmd("sed -i.bak s/\"1\"/\"2\"/ " ++ Dir ++ "/relx.config")]),
	%% We need Ranch to be fetched first in order to copy the current appup
	%% and optionally update its version when we are not on a tag.
	ct:log("~s~n", [os:cmd(Make ++ " -C " ++ Dir ++ " deps")]),
	ct:log("~s~n", [os:cmd("cp " ++ Dir ++ "/../../src/ranch.appup "
		++ Dir ++ "/deps/ranch/src/")]),
	case CommitOrTag of
		tag -> ok;
		commit ->
			%% Force the rebuild of Ranch.
			ct:log("~s~n", [os:cmd(Make ++ " -C " ++ Dir ++ "/deps/ranch clean")]),
			%% Update the Ranch version so that the upgrade can be applied.
			ProjectVersion = os:cmd("grep \"PROJECT_VERSION = \" " ++ Dir ++ "/deps/ranch/Makefile"),
			ct:log(ProjectVersion),
			["PROJECT_VERSION = " ++ Vsn0|_] = string:lexemes(ProjectVersion, "\n"),
			[A, B|Tail] = string:lexemes(Vsn0, "."),
			Vsn = binary_to_list(iolist_to_binary([A, $., B, ".9", lists:join($., Tail)])),
			ct:log("Changing Ranch version from ~s to ~s~n", [Vsn0, Vsn]),
			ct:log("~s~n", [os:cmd(
				"sed -i.bak s/\"PROJECT_VERSION = .*\"/\"PROJECT_VERSION = " ++ Vsn ++ "\"/ "
					++ Dir ++ "/deps/ranch/Makefile"
			)]),
			%% The version in the appup must be the same as PROJECT_VERSION.
			ct:log("~s~n", [os:cmd(
				"sed -i.bak s/\"" ++ Vsn0 ++ "\"/\"" ++ Vsn ++ "\"/ "
					++ Dir ++ "/deps/ranch/src/ranch.appup"
			)])
	end,
	ct:log("~s~n", [os:cmd(Make ++ " -C " ++ Dir ++ " relup")]).

%% Copy the tarball in the correct location and upgrade.
do_upgrade(Example) ->
	ExampleStr = atom_to_list(Example),
	{Dir, Rel, _} = do_get_paths(Example),
	ct:log("~s~n", [os:cmd("cp "
		++ Dir ++ "/_rel/" ++ ExampleStr
			++ "_example/" ++ ExampleStr ++ "_example-2.tar.gz "
		++ Dir ++ "/_rel/" ++ ExampleStr
			++ "_example/releases/2/" ++ ExampleStr ++ "_example.tar.gz")]),
	ct:log("~s~n", [os:cmd(Rel ++ " upgrade \"2\"")]),
	ct:log("~s~n", [os:cmd(Rel ++ " eval 'application:info()'")]).

do_downgrade(Example) ->
	{_, Rel, _} = do_get_paths(Example),
	ct:log("~s~n", [os:cmd(Rel ++ " downgrade \"1\"")]),
	ct:log("~s~n", [os:cmd(Rel ++ " eval 'application:info()'")]).

%% Tests.

upgrade_ranch_one_conn(Config) ->
	Example = tcp_echo,
	Port = 5555,
	try
		%% Build and start the example release using the previous Ranch version.
		CommitOrTag = do_use_ranch_previous(Example),
		do_compile_and_start(Example, Config),
		%% Establish a connection and check that it works.
		{ok, S} = gen_tcp:connect("localhost", Port, [{active, false}, binary]),
		ok = gen_tcp:send(S, "Hello!"),
		{ok, <<"Hello!">>} = gen_tcp:recv(S, 0, 1000),
		%% Update Ranch to master then build a release upgrade.
		do_use_ranch_commit(Example, "master"),
		do_build_relup(Example, CommitOrTag),
		%% Perform the upgrade, then check that our connection is still up.
		do_upgrade(Example),
		ok = gen_tcp:send(S, "Hello!"),
		{ok, <<"Hello!">>} = gen_tcp:recv(S, 0, 1000),
		%% Check that new connections are still accepted.
		{ok, _} = gen_tcp:connect("localhost", Port, [{active, false}, binary]),
		%% Perform the downgrade, then check that our connection is still up.
		do_downgrade(Example),
		ok = gen_tcp:send(S, "Hello!"),
		{ok, <<"Hello!">>} = gen_tcp:recv(S, 0, 1000),
		%% Check that new connections are still accepted.
		{ok, _} = gen_tcp:connect("localhost", Port, [{active, false}, binary]),
		ok
	after
		do_stop(tcp_echo)
	end.

%% @todo upgrade_ranch_max_conn