%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2014. 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% %% -module(erl_prim_loader_SUITE). -include_lib("kernel/include/file.hrl"). -include_lib("common_test/include/ct.hrl"). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). -export([get_path/1, set_path/1, get_file/1, normalize_and_backslash/1, inet_existing/1, inet_coming_up/1, inet_disconnects/1, multiple_slaves/1, file_requests/1, local_archive/1, remote_archive/1, primary_archive/1, virtual_dir_in_archive/1]). -export([init_per_testcase/2, end_per_testcase/2]). %%----------------------------------------------------------------- %% Test suite for erl_prim_loader. (Most code is run during system start/stop.) %%----------------------------------------------------------------- suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [get_path, set_path, get_file, normalize_and_backslash, inet_existing, inet_coming_up, inet_disconnects, multiple_slaves, file_requests, local_archive, remote_archive, primary_archive, virtual_dir_in_archive]. groups() -> []. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) -> Dog=?t:timetrap(?t:minutes(3)), [{watchdog, Dog}|Config]. end_per_testcase(_Func, Config) -> Dog=?config(watchdog, Config), ?t:timetrap_cancel(Dog). get_path(doc) -> []; get_path(Config) when is_list(Config) -> ?line case erl_prim_loader:get_path() of {ok, Path} when is_list(Path) -> ok; _ -> test_server:fail(get_path) end, ok. set_path(doc) -> []; set_path(Config) when is_list(Config) -> ?line {ok, Path} = erl_prim_loader:get_path(), ?line ok = erl_prim_loader:set_path(Path), ?line {ok, Path} = erl_prim_loader:get_path(), NewPath = Path ++ ["dummy_dir","/dummy_dir/dummy_dir"], ?line ok = erl_prim_loader:set_path(NewPath), ?line {ok, NewPath} = erl_prim_loader:get_path(), ?line ok = erl_prim_loader:set_path(Path), % Reset path. ?line {ok, Path} = erl_prim_loader:get_path(), ?line {'EXIT',_} = (catch erl_prim_loader:set_path(not_a_list)), ?line {ok, Path} = erl_prim_loader:get_path(), ok. get_file(doc) -> []; get_file(Config) when is_list(Config) -> ?line case erl_prim_loader:get_file("lists" ++ code:objfile_extension()) of {ok,Bin,File} when is_binary(Bin), is_list(File) -> ok; _ -> test_server:fail(get_valid_file) end, ?line error = erl_prim_loader:get_file("duuuuuuummmy_file"), ?line error = erl_prim_loader:get_file(duuuuuuummmy_file), ?line error = erl_prim_loader:get_file({dummy}), ok. normalize_and_backslash(Config) -> %% Test OTP-11170 case os:type() of {win32,_} -> {skip, "not on windows"}; _ -> test_normalize_and_backslash(Config) end. test_normalize_and_backslash(Config) -> PrivDir = ?config(priv_dir,Config), Dir = filename:join(PrivDir,"\\"), File = filename:join(Dir,"file-OTP-11170"), ok = file:make_dir(Dir), ok = file:write_file(File,"a file to test OTP-11170"), {ok,["file-OTP-11170"]} = file:list_dir(Dir), {ok,["file-OTP-11170"]} = erl_prim_loader:list_dir(Dir), ok = file:delete(File), ok = file:del_dir(Dir), ok. inet_existing(doc) -> ["Start a node using the 'inet' loading method, ", "from an already started boot server."]; inet_existing(Config) when is_list(Config) -> Name = erl_prim_test_inet_existing, BootPid = start_boot_server(), Node = start_node_using_inet(Name), {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), stop_node(Node), unlink(BootPid), exit(BootPid, kill), ok. inet_coming_up(doc) -> ["Start a node using the 'inet' loading method, ", "but start the boot server afterwards."]; inet_coming_up(Config) when is_list(Config) -> Name = erl_prim_test_inet_coming_up, Node = start_node_using_inet(Name, [{wait,false}]), %% Wait a while, then start boot server, and wait for node to start. test_server:sleep(test_server:seconds(6)), BootPid = start_boot_server(), wait_really_started(Node, 25), %% Check loader argument, then cleanup. {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), stop_node(Node), unlink(BootPid), exit(BootPid, kill), ok. wait_really_started(Node, 0) -> test_server:fail({not_booted,Node}); wait_really_started(Node, N) -> case rpc:call(Node, init, get_status, []) of {started, _} -> ok; _ -> test_server:sleep(1000), wait_really_started(Node, N - 1) end. inet_disconnects(doc) -> ["Start a node using the 'inet' loading method, ", "then lose the connection."]; inet_disconnects(Config) when is_list(Config) -> case test_server:is_native(erl_boot_server) of true -> {skip,"erl_boot_server is native"}; false -> Name = erl_prim_test_inet_disconnects, BootPid = start_boot_server(), unlink(BootPid), Self = self(), %% This process shuts down the boot server during loading. Stopper = spawn_link(fun() -> stop_boot(BootPid, Self) end), receive {Stopper,ready} -> ok end, %% Let the loading begin... Node = start_node_using_inet(Name, [{wait,false}]), %% When the stopper is ready, the slave node should be %% looking for a boot server again. receive {Stopper,ok} -> ok; {Stopper,{error,Reason}} -> ?line ?t:fail(Reason) after 60000 -> ?line ?t:fail(stopper_died) end, %% Start new boot server to see that loading is continued. BootPid2 = start_boot_server(), wait_really_started(Node, 25), {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), stop_node(Node), unlink(BootPid2), exit(BootPid2, kill), ok end. %% Trace boot server calls and stop the server before loading is finished. stop_boot(BootPid, Super) -> erlang:trace(all, true, [call]), 1 = erlang:trace_pattern({erl_boot_server,send_file_result,3}, true, [local]), BootRef = erlang:monitor(process, BootPid), Super ! {self(),ready}, Result = get_calls(100, BootPid), exit(BootPid, kill), erlang:trace_pattern({erl_boot_server,send_file_result,3}, false, [local]), erlang:trace(all, false, [call]), receive {'DOWN',BootRef,_,_, killed} -> ok end, Super ! {self(),Result}. get_calls(0, _) -> ok; get_calls(Count, Pid) -> receive {trace,_,call,_MFA} -> get_calls(Count-1, Pid) after 10000 -> {error,{trace_msg_timeout,Count}} end. multiple_slaves(doc) -> ["Start nodes in parallell, all using the 'inet' loading method, ", "verify that the boot server manages"]; multiple_slaves(Config) when is_list(Config) -> ?line Name = erl_prim_test_multiple_slaves, ?line Host = host(), ?line IpStr = ip_str(Host), Args = " -loader inet -hosts " ++ IpStr, NoOfNodes = 10, % no of slave nodes to be started NamesAndNodes = lists:map(fun(N) -> NameN = atom_to_list(Name) ++ integer_to_list(N), NodeN = NameN ++ "@" ++ Host, {list_to_atom(NameN),list_to_atom(NodeN)} end, lists:seq(1, NoOfNodes)), ?line Nodes = start_multiple_nodes(NamesAndNodes, Args, []), %% "queue up" the nodes to wait for the boot server to respond %% (note: test_server supervises each node start by accept() %% on a socket, the timeout value for the accept has to be quite %% long for this test to work). ?line test_server:sleep(test_server:seconds(5)), %% start the code loading circus! BootPid = start_boot_server(), %% give the nodes a chance to boot up before attempting to stop them ?line test_server:sleep(test_server:seconds(10)), ?line wait_and_shutdown(lists:reverse(Nodes), 30), ?line unlink(BootPid), ?line exit(BootPid, kill), ok. start_multiple_nodes([{Name,Node} | NNs], Args, Started) -> ?line {ok,Node} = start_node(Name, Args, [{wait, false}]), start_multiple_nodes(NNs, Args, [Node | Started]); start_multiple_nodes([], _, Nodes) -> Nodes. wait_and_shutdown([Node | Nodes], Tries) -> ?line wait_really_started(Node, Tries), ?line {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), ?line stop_node(Node), wait_and_shutdown(Nodes, Tries); wait_and_shutdown([], _) -> ok. file_requests(doc) -> ["Start a node using the 'inet' loading method, ", "verify that the boot server responds to file requests."]; file_requests(Config) when is_list(Config) -> ?line {ok, Node, BootPid} = complete_start_node(erl_prim_test_file_req), %% compare with results from file server calls (the %% boot server uses the same file sys and cwd) {ok,Files} = file:list_dir("."), io:format("Files: ~p~n",[Files]), ?line {ok,Files} = rpc:call(Node, erl_prim_loader, list_dir, ["."]), {ok,Info} = file:read_file_info(code:which(test_server)), ?line {ok,Info} = rpc:call(Node, erl_prim_loader, read_file_info, [code:which(test_server)]), PrivDir = ?config(priv_dir,Config), Dir = filename:join(PrivDir,?MODULE_STRING++"_file_requests"), ok = file:make_dir(Dir), Alias = filename:join(Dir,"symlink"), case file:make_symlink(code:which(test_server), Alias) of {error, enotsup} -> %% Links not supported on this platform ok; {error, eperm} -> {win32,_} = os:type(), %% Windows user not privileged to create symlinks" ok; ok -> %% Reading file info for link should return file info for %% link target {ok,Info} = rpc:call(Node, erl_prim_loader, read_file_info, [Alias]), #file_info{type=regular} = Info, {ok,#file_info{type=symlink}} = rpc:call(Node, erl_prim_loader, read_link_info, [Alias]) end, {ok,Cwd} = file:get_cwd(), ?line {ok,Cwd} = rpc:call(Node, erl_prim_loader, get_cwd, []), case file:get_cwd("C:") of {error,enotsup} -> ok; {ok,DCwd} -> ?line {ok,DCwd} = rpc:call(Node, erl_prim_loader, get_cwd, ["C:"]) end, ?line stop_node(Node), ?line unlink(BootPid), ?line exit(BootPid, kill), ok. local_archive(suite) -> []; local_archive(doc) -> ["Read files from local archive."]; local_archive(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), KernelDir = filename:basename(code:lib_dir(kernel)), Archive = filename:join([PrivDir, KernelDir ++ init:archive_extension()]), file:delete(Archive), ?line {ok, Archive} = create_archive(Archive, [KernelDir]), Node = node(), BeamName = "inet.beam", ?line ok = test_archive(Node, Archive, KernelDir, BeamName), %% Cleanup ok = rpc:call(Node, erl_prim_loader, purge_archive_cache, []), ?line ok = file:delete(Archive), ok. remote_archive(suite) -> {req, [{local_slave_nodes, 1}, {time, 10}]}; remote_archive(doc) -> ["Read files from remote archive."]; remote_archive(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), KernelDir = filename:basename(code:lib_dir(kernel)), Archive = filename:join([PrivDir, KernelDir ++ init:archive_extension()]), file:delete(Archive), ?line {ok, Archive} = create_archive(Archive, [KernelDir]), ?line {ok, Node, BootPid} = complete_start_node(remote_archive), BeamName = "inet.beam", ?line ok = test_archive(Node, Archive, KernelDir, BeamName), %% Cleanup ?line stop_node(Node), ?line unlink(BootPid), ?line exit(BootPid, kill), ok. primary_archive(suite) -> {req, [{local_slave_nodes, 1}, {time, 10}]}; primary_archive(doc) -> ["Read files from primary archive."]; primary_archive(Config) when is_list(Config) -> %% Copy the orig files to priv_dir PrivDir = ?config(priv_dir, Config), Archive = filename:join([PrivDir, "primary_archive.zip"]), file:delete(Archive), DataDir = ?config(data_dir, Config), ?line {ok, _} = zip:create(Archive, ["primary_archive"], [{compress, []}, {cwd, DataDir}]), ?line {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]), TopDir = filename:join([PrivDir, "primary_archive"]), %% Compile the code DictDir = "primary_archive_dict-1.0", DummyDir = "primary_archive_dummy", ?line ok = compile_app(TopDir, DictDir), ?line ok = compile_app(TopDir, DummyDir), %% Create the archive {ok, TopFiles} = file:list_dir(TopDir), ?line {ok, {_, ArchiveBin}} = zip:create(Archive, TopFiles, [memory, {compress, []}, {cwd, TopDir}]), %% Use temporary node to simplify cleanup ?line Cookie = atom_to_list(erlang:get_cookie()), ?line Args = " -setcookie " ++ Cookie, ?line {ok,Node} = start_node(primary_archive, Args), ?line wait_really_started(Node, 25), ?line {_,_,_} = rpc:call(Node, erlang, date, []), %% Set primary archive ExpectedEbins = [Archive, DictDir ++ "/ebin", DummyDir ++ "/ebin"], io:format("ExpectedEbins: ~p\n", [ExpectedEbins]), ?line {ok, FileInfo} = prim_file:read_file_info(Archive), ?line {ok, Ebins} = rpc:call(Node, erl_prim_loader, set_primary_archive, [Archive, ArchiveBin, FileInfo, fun escript:parse_file/1]), ?line ExpectedEbins = lists:sort(Ebins), % assert ?line {ok, TopFiles2} = rpc:call(Node, erl_prim_loader, list_dir, [Archive]), ?line [DictDir, DummyDir] = lists:sort(TopFiles2), BeamName = "primary_archive_dict_app.beam", ?line ok = test_archive(Node, Archive, DictDir, BeamName), %% Cleanup ?line {ok, []} = rpc:call(Node, erl_prim_loader, set_primary_archive, [undefined, undefined, undefined, fun escript:parse_file/1]), ?line stop_node(Node), ?line ok = file:delete(Archive), ok. test_archive(Node, TopDir, AppDir, BeamName) -> %% List dir io:format("test_archive: ~p\n", [rpc:call(Node, erl_prim_loader, list_dir, [TopDir])]), ?line {ok, TopFiles} = rpc:call(Node, erl_prim_loader, list_dir, [TopDir]), ?line true = lists:member(AppDir, TopFiles), AbsAppDir = TopDir ++ "/" ++ AppDir, ?line {ok, AppFiles} = rpc:call(Node, erl_prim_loader, list_dir, [AbsAppDir]), ?line true = lists:member("ebin", AppFiles), Ebin = AbsAppDir ++ "/ebin", ?line {ok, EbinFiles} = rpc:call(Node, erl_prim_loader, list_dir, [Ebin]), Beam = Ebin ++ "/" ++ BeamName, ?line true = lists:member(BeamName, EbinFiles), ?line error = rpc:call(Node, erl_prim_loader, list_dir, [TopDir ++ "/no_such_file"]), ?line error = rpc:call(Node, erl_prim_loader, list_dir, [TopDir ++ "/ebin/no_such_file"]), %% File info ?line {ok, #file_info{type = directory}} = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir]), ?line {ok, #file_info{type = directory}} = rpc:call(Node, erl_prim_loader, read_file_info, [Ebin]), ?line {ok, #file_info{type = regular} = FI} = rpc:call(Node, erl_prim_loader, read_file_info, [Beam]), ?line error = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir ++ "/no_such_file"]), ?line error = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir ++ "/ebin/no_such_file"]), %% Get file ?line {ok, Bin, Beam} = rpc:call(Node, erl_prim_loader, get_file, [Beam]), ?line if FI#file_info.size =:= byte_size(Bin) -> ok; true -> exit({FI#file_info.size, byte_size(Bin)}) end, ?line error = rpc:call(Node, erl_prim_loader, get_file, ["/no_such_file"]), ?line error = rpc:call(Node, erl_prim_loader, get_file, ["/ebin/no_such_file"]), ok. create_archive(Archive, AppDirs) -> LibDir = code:lib_dir(), Opts = [{compress, []}, {cwd, LibDir}], io:format("zip:create(~p,\n\t~p,\n\t~p).\n", [Archive, AppDirs, Opts]), zip:create(Archive, AppDirs, Opts). virtual_dir_in_archive(suite) -> []; virtual_dir_in_archive(doc) -> ["Read virtual directories from archive."]; virtual_dir_in_archive(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), Data = <<"A little piece of data.">>, ArchiveBase = "archive_with_virtual_dirs", Archive = filename:join([PrivDir, ArchiveBase ++ init:archive_extension()]), FileBase = "a_data_file.beam", EbinBase = "ebin", FileInArchive = filename:join([ArchiveBase, EbinBase, FileBase]), BinFiles = [{FileInArchive, Data}], Opts = [{compress, []}], ?line file:delete(Archive), io:format("zip:create(~p,\n\t~p,\n\t~p).\n", [Archive, BinFiles, Opts]), ?line {ok, Archive} = zip:create(Archive, BinFiles, Opts), %% Verify that there is no directories ?line {ok, BinFiles} = zip:unzip(Archive, [memory]), FullPath = filename:join([Archive, FileInArchive]), ?line {ok, _} = erl_prim_loader:read_file_info(FullPath), %% Read one virtual dir EbinDir = filename:dirname(FullPath), ?line {ok, _} = erl_prim_loader:read_file_info(EbinDir), ?line {ok, [FileBase]} = erl_prim_loader:list_dir(EbinDir), %% Read another virtual dir AppDir = filename:dirname(EbinDir), ?line {ok, _} = erl_prim_loader:read_file_info(AppDir), ?line {ok, [EbinBase]} = erl_prim_loader:list_dir(AppDir), %% Cleanup ok = erl_prim_loader:purge_archive_cache(), ?line ok = file:delete(Archive), ok. %%% %%% Helper functions. %%% complete_start_node(Name) -> BootPid = start_boot_server(), Node = start_node_using_inet(Name), wait_really_started(Node, 25), {ok, Node, BootPid}. start_boot_server() -> %% Many linux systems define: %% 127.0.0.1 localhost %% 127.0.1.1 somehostname %% Therefore, to allow the tests to work on those kind of systems, %% also include "localhost" in the list of allowed hosts. Hosts = [host(),ip_str("localhost")], {ok,BootPid} = erl_boot_server:start_link(Hosts), BootPid. start_node_using_inet(Name) -> start_node_using_inet(Name, []). start_node_using_inet(Name, Opts) -> Host = host(), IpStr = ip_str(Host), Args = " -loader inet -hosts " ++ IpStr, {ok,Node} = start_node(Name, Args, Opts), Node. ip_str({A, B, C, D}) -> lists:concat([A, ".", B, ".", C, ".", D]); ip_str(Host) -> {ok,Ip} = inet:getaddr(Host, inet), ip_str(Ip). start_node(Name, Args) -> start_node(Name, Args, []). start_node(Name, Args, Opts) -> Opts2 = [{args, Args}|Opts], io:format("test_server:start_node(~p, peer, ~p).\n", [Name, Opts2]), Res = test_server:start_node(Name, peer, Opts2), io:format("start_node -> ~p\n", [Res]), Res. host() -> {ok,Host} = inet:gethostname(), Host. stop_node(Node) -> test_server:stop_node(Node). compile_app(TopDir, AppName) -> AppDir = filename:join([TopDir, AppName]), SrcDir = filename:join([AppDir, "src"]), OutDir = filename:join([AppDir, "ebin"]), ?line {ok, Files} = file:list_dir(SrcDir), compile_files(Files, SrcDir, OutDir). compile_files([File | Files], SrcDir, OutDir) -> case filename:extension(File) of ".erl" -> AbsFile = filename:join([SrcDir, File]), case compile:file(AbsFile, [{outdir, OutDir}]) of {ok, _Mod} -> compile_files(Files, SrcDir, OutDir); Error -> {compilation_error, AbsFile, OutDir, Error} end; _ -> compile_files(Files, SrcDir, OutDir) end; compile_files([], _, _) -> ok.