%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -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_testcase/2,end_per_testcase/2, 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, get_modules/1]). -define(PRIM_FILE, prim_file). %%----------------------------------------------------------------- %% Test suite for erl_prim_loader. (Most code is run during system start/stop.) %%----------------------------------------------------------------- suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,3}}]. 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, get_modules]. 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) -> Config. end_per_testcase(_Func, _Config) -> ok. get_path(Config) when is_list(Config) -> case erl_prim_loader:get_path() of {ok, Path} when is_list(Path) -> ok; _ -> ct:fail(get_path) end, ok. set_path(Config) when is_list(Config) -> {ok, Path} = erl_prim_loader:get_path(), ok = erl_prim_loader:set_path(Path), {ok, Path} = erl_prim_loader:get_path(), NewPath = Path ++ ["dummy_dir","/dummy_dir/dummy_dir"], ok = erl_prim_loader:set_path(NewPath), {ok, NewPath} = erl_prim_loader:get_path(), ok = erl_prim_loader:set_path(Path), % Reset path. {ok, Path} = erl_prim_loader:get_path(), {'EXIT',_} = (catch erl_prim_loader:set_path(not_a_list)), {ok, Path} = erl_prim_loader:get_path(), ok. get_file(Config) when is_list(Config) -> case erl_prim_loader:get_file("lists" ++ code:objfile_extension()) of {ok,Bin,File} when is_binary(Bin), is_list(File) -> ok; _ -> ct:fail(get_valid_file) end, error = erl_prim_loader:get_file("duuuuuuummmy_file"), error = erl_prim_loader:get_file(duuuuuuummmy_file), error = erl_prim_loader:get_file({dummy}), ok. get_modules(Config) -> case test_server:is_cover() of false -> do_get_modules(Config); true -> {skip,"Cover"} end. do_get_modules(Config) -> PrivDir = proplists:get_value(priv_dir, Config), NotADir = atom_to_list(?FUNCTION_NAME) ++ "_not_a_dir", ok = file:write_file(filename:join(PrivDir, NotADir), <<>>), ok = file:set_cwd(PrivDir), MsGood = lists:sort([lists,gen_server,gb_trees,code_server]), Ms = [certainly_not_existing|MsGood], SuccExp = [begin F = code:which(M), {ok,Code} = file:read_file(F), {M,{F,erlang:md5(Code)}} end || M <- MsGood], FailExp = [{certainly_not_existing,enoent}], io:format("SuccExp = ~p\n", [SuccExp]), io:format("FailExp = ~p\n", [FailExp]), Path = code:get_path(), Process = fun(_, F, Code) -> {ok,{F,erlang:md5(Code)}} end, {ok,{SuccExp,FailExp}} = get_modules_sorted(Ms, Process, Path), %% Test that an 'enotdir' error can be handled. {ok,{SuccExp,FailExp}} = get_modules_sorted(Ms, Process, [NotADir|Path]), Name = inet_get_modules, {ok, Node, BootPid} = complete_start_node(Name), ThisDir = filename:dirname(code:which(?MODULE)), true = rpc:call(Node, code, add_patha, [ThisDir]), _ = rpc:call(Node, code, ensure_loaded, [?MODULE]), {ok,{InetSucc,FailExp}} = rpc:call(Node, erl_prim_loader, get_modules, [Ms,Process,Path]), SuccExp = lists:sort(InetSucc), stop_node(Node), unlink(BootPid), exit(BootPid, kill), ok. get_modules_sorted(Ms, Process, Path) -> case erl_prim_loader:get_modules(Ms, Process, Path) of {ok,{Succ,FailExp}} -> {ok,{lists:sort(Succ),lists:sort(FailExp)}}; Other -> Other end. 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 = proplists:get_value(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. %% 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. %% 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. ct:sleep({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) -> ct:fail({not_booted,Node}); wait_really_started(Node, N) -> case rpc:call(Node, init, get_status, []) of {started, _} -> ok; _ -> ct:sleep(1000), wait_really_started(Node, N - 1) end. %% 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}} -> ct:fail(Reason) after 60000 -> ct: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. %% Start nodes in parallel, all using the 'inet' loading method; %% verify that the boot server manages. multiple_slaves(Config) when is_list(Config) -> Name = erl_prim_test_multiple_slaves, Host = host(), 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)), 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). ct:sleep({seconds,5}), %% start the code loading circus! BootPid = start_boot_server(), %% give the nodes a chance to boot up before attempting to stop them ct:sleep({seconds,10}), wait_and_shutdown(lists:reverse(Nodes), 30), unlink(BootPid), exit(BootPid, kill), ok. start_multiple_nodes([{Name,Node} | NNs], Args, Started) -> {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) -> wait_really_started(Node, Tries), {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), stop_node(Node), wait_and_shutdown(Nodes, Tries); wait_and_shutdown([], _) -> ok. %% Start a node using the 'inet' loading method, %% verify that the boot server responds to file requests. file_requests(Config) when is_list(Config) -> {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]), {ok,Files} = rpc:call(Node, erl_prim_loader, list_dir, ["."]), {ok,Info} = file:read_file_info(code:which(test_server)), {ok,Info} = rpc:call(Node, erl_prim_loader, read_file_info, [code:which(test_server)]), PrivDir = proplists:get_value(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(), {ok,Cwd} = rpc:call(Node, erl_prim_loader, get_cwd, []), case file:get_cwd("C:") of {error,enotsup} -> ok; {ok,DCwd} -> {ok,DCwd} = rpc:call(Node, erl_prim_loader, get_cwd, ["C:"]) end, stop_node(Node), unlink(BootPid), exit(BootPid, kill), ok. %% Read files from local archive. local_archive(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), KernelDir = filename:basename(code:lib_dir(kernel)), Archive = filename:join([PrivDir, KernelDir ++ init:archive_extension()]), file:delete(Archive), {ok, Archive} = create_archive(Archive, [KernelDir]), Node = node(), BeamName = "inet.beam", ok = test_archive(Node, Archive, KernelDir, BeamName), %% Cleanup ok = rpc:call(Node, erl_prim_loader, purge_archive_cache, []), ok = file:delete(Archive), ok. %% Read files from remote archive. remote_archive(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), KernelDir = filename:basename(code:lib_dir(kernel)), Archive = filename:join([PrivDir, KernelDir ++ init:archive_extension()]), file:delete(Archive), {ok, Archive} = create_archive(Archive, [KernelDir]), {ok, Node, BootPid} = complete_start_node(remote_archive), BeamName = "inet.beam", ok = test_archive(Node, Archive, KernelDir, BeamName), %% Cleanup stop_node(Node), unlink(BootPid), exit(BootPid, kill), ok. %% Read files from primary archive. primary_archive(Config) when is_list(Config) -> %% Copy the orig files to priv_dir PrivDir = proplists:get_value(priv_dir, Config), Archive = filename:join([PrivDir, "primary_archive.zip"]), file:delete(Archive), DataDir = proplists:get_value(data_dir, Config), {ok, _} = zip:create(Archive, ["primary_archive"], [{compress, []}, {cwd, DataDir}]), {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", ok = compile_app(TopDir, DictDir), ok = compile_app(TopDir, DummyDir), %% Create the archive {ok, TopFiles} = file:list_dir(TopDir), {ok, {_, ArchiveBin}} = zip:create(Archive, TopFiles, [memory, {compress, []}, {cwd, TopDir}]), %% Use temporary node to simplify cleanup Cookie = atom_to_list(erlang:get_cookie()), Args = " -setcookie " ++ Cookie, {ok,Node} = start_node(primary_archive, Args), wait_really_started(Node, 25), {_,_,_} = rpc:call(Node, erlang, date, []), %% Set primary archive ExpectedEbins = [Archive, DictDir ++ "/ebin", DummyDir ++ "/ebin"], io:format("ExpectedEbins: ~p\n", [ExpectedEbins]), {ok, FileInfo} = ?PRIM_FILE:read_file_info(Archive), {ok, Ebins} = rpc:call(Node, erl_prim_loader, set_primary_archive, [Archive, ArchiveBin, FileInfo, fun escript:parse_file/1]), ExpectedEbins = lists:sort(Ebins), % assert {ok, TopFiles2} = rpc:call(Node, erl_prim_loader, list_dir, [Archive]), [DictDir, DummyDir] = lists:sort(TopFiles2), BeamName = "primary_archive_dict_app.beam", ok = test_archive(Node, Archive, DictDir, BeamName), %% Cleanup {ok, []} = rpc:call(Node, erl_prim_loader, set_primary_archive, [undefined, undefined, undefined, fun escript:parse_file/1]), stop_node(Node), 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])]), {ok, TopFiles} = rpc:call(Node, erl_prim_loader, list_dir, [TopDir]), true = lists:member(AppDir, TopFiles), AbsAppDir = TopDir ++ "/" ++ AppDir, {ok, AppFiles} = rpc:call(Node, erl_prim_loader, list_dir, [AbsAppDir]), true = lists:member("ebin", AppFiles), Ebin = AbsAppDir ++ "/ebin", {ok, EbinFiles} = rpc:call(Node, erl_prim_loader, list_dir, [Ebin]), Beam = Ebin ++ "/" ++ BeamName, true = lists:member(BeamName, EbinFiles), error = rpc:call(Node, erl_prim_loader, list_dir, [TopDir ++ "/no_such_file"]), error = rpc:call(Node, erl_prim_loader, list_dir, [TopDir ++ "/ebin/no_such_file"]), %% File info {ok, #file_info{type = directory}} = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir]), {ok, #file_info{type = directory}} = rpc:call(Node, erl_prim_loader, read_file_info, [Ebin]), {ok, #file_info{type = regular} = FI} = rpc:call(Node, erl_prim_loader, read_file_info, [Beam]), error = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir ++ "/no_such_file"]), error = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir ++ "/ebin/no_such_file"]), %% Get file {ok, Bin, Beam} = rpc:call(Node, erl_prim_loader, get_file, [Beam]), if FI#file_info.size =:= byte_size(Bin) -> ok; true -> exit({FI#file_info.size, byte_size(Bin)}) end, error = rpc:call(Node, erl_prim_loader, get_file, ["/no_such_file"]), 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). %% Read virtual directories from archive. virtual_dir_in_archive(Config) when is_list(Config) -> PrivDir = proplists:get_value(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, []}], file:delete(Archive), io:format("zip:create(~p,\n\t~p,\n\t~p).\n", [Archive, BinFiles, Opts]), {ok, Archive} = zip:create(Archive, BinFiles, Opts), %% Verify that there is no directories {ok, BinFiles} = zip:unzip(Archive, [memory]), FullPath = filename:join([Archive, FileInArchive]), {ok, _} = erl_prim_loader:read_file_info(FullPath), %% Read one virtual dir EbinDir = filename:dirname(FullPath), {ok, _} = erl_prim_loader:read_file_info(EbinDir), {ok, [FileBase]} = erl_prim_loader:list_dir(EbinDir), %% Read another virtual dir AppDir = filename:dirname(EbinDir), {ok, _} = erl_prim_loader:read_file_info(AppDir), {ok, [EbinBase]} = erl_prim_loader:list_dir(AppDir), %% Cleanup ok = erl_prim_loader:purge_archive_cache(), 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"]), {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.