1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(erl_prim_loader_SUITE). 21 22-include_lib("kernel/include/file.hrl"). 23-include_lib("common_test/include/ct.hrl"). 24 25-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 26 init_per_testcase/2,end_per_testcase/2, 27 init_per_group/2,end_per_group/2]). 28 29-export([get_path/1, set_path/1, get_file/1, normalize_and_backslash/1, 30 inet_existing/1, inet_coming_up/1, inet_disconnects/1, 31 multiple_slaves/1, file_requests/1, 32 local_archive/1, remote_archive/1, 33 primary_archive/1, virtual_dir_in_archive/1, 34 get_modules/1]). 35 36-define(PRIM_FILE, prim_file). 37 38%%----------------------------------------------------------------- 39%% Test suite for erl_prim_loader. (Most code is run during system start/stop.) 40%%----------------------------------------------------------------- 41 42suite() -> 43 [{ct_hooks,[ts_install_cth]}, 44 {timetrap,{minutes,3}}]. 45 46all() -> 47 [get_path, set_path, get_file, 48 normalize_and_backslash, inet_existing, 49 inet_coming_up, inet_disconnects, multiple_slaves, 50 file_requests, local_archive, remote_archive, 51 primary_archive, virtual_dir_in_archive, 52 get_modules]. 53 54groups() -> 55 []. 56 57init_per_suite(Config) -> 58 Config. 59 60end_per_suite(_Config) -> 61 ok. 62 63init_per_group(_GroupName, Config) -> 64 Config. 65 66end_per_group(_GroupName, Config) -> 67 Config. 68 69 70init_per_testcase(_Func, Config) -> 71 Config. 72 73end_per_testcase(_Func, _Config) -> 74 ok. 75 76get_path(Config) when is_list(Config) -> 77 case erl_prim_loader:get_path() of 78 {ok, Path} when is_list(Path) -> 79 ok; 80 _ -> 81 ct:fail(get_path) 82 end, 83 ok. 84 85set_path(Config) when is_list(Config) -> 86 {ok, Path} = erl_prim_loader:get_path(), 87 ok = erl_prim_loader:set_path(Path), 88 {ok, Path} = erl_prim_loader:get_path(), 89 NewPath = Path ++ ["dummy_dir","/dummy_dir/dummy_dir"], 90 ok = erl_prim_loader:set_path(NewPath), 91 {ok, NewPath} = erl_prim_loader:get_path(), 92 93 ok = erl_prim_loader:set_path(Path), % Reset path. 94 {ok, Path} = erl_prim_loader:get_path(), 95 96 {'EXIT',_} = (catch erl_prim_loader:set_path(not_a_list)), 97 {ok, Path} = erl_prim_loader:get_path(), 98 ok. 99 100get_file(Config) when is_list(Config) -> 101 case erl_prim_loader:get_file("lists" ++ code:objfile_extension()) of 102 {ok,Bin,File} when is_binary(Bin), is_list(File) -> 103 ok; 104 _ -> 105 ct:fail(get_valid_file) 106 end, 107 error = erl_prim_loader:get_file("duuuuuuummmy_file"), 108 error = erl_prim_loader:get_file(duuuuuuummmy_file), 109 error = erl_prim_loader:get_file({dummy}), 110 ok. 111 112get_modules(Config) -> 113 case test_server:is_cover() of 114 false -> do_get_modules(Config); 115 true -> {skip,"Cover"} 116 end. 117 118do_get_modules(Config) -> 119 PrivDir = proplists:get_value(priv_dir, Config), 120 NotADir = atom_to_list(?FUNCTION_NAME) ++ "_not_a_dir", 121 ok = file:write_file(filename:join(PrivDir, NotADir), <<>>), 122 ok = file:set_cwd(PrivDir), 123 124 MsGood = lists:sort([lists,gen_server,gb_trees,code_server]), 125 Ms = [certainly_not_existing|MsGood], 126 SuccExp = [begin 127 F = code:which(M), 128 {ok,Code} = file:read_file(F), 129 {M,{F,erlang:md5(Code)}} 130 end || M <- MsGood], 131 FailExp = [{certainly_not_existing,enoent}], 132 133 io:format("SuccExp = ~p\n", [SuccExp]), 134 io:format("FailExp = ~p\n", [FailExp]), 135 136 Path = code:get_path(), 137 Process = fun(_, F, Code) -> {ok,{F,erlang:md5(Code)}} end, 138 {ok,{SuccExp,FailExp}} = get_modules_sorted(Ms, Process, Path), 139 140 %% Test that an 'enotdir' error can be handled. 141 {ok,{SuccExp,FailExp}} = get_modules_sorted(Ms, Process, [NotADir|Path]), 142 143 Name = inet_get_modules, 144 {ok, Node, BootPid} = complete_start_node(Name), 145 ThisDir = filename:dirname(code:which(?MODULE)), 146 true = rpc:call(Node, code, add_patha, [ThisDir]), 147 _ = rpc:call(Node, code, ensure_loaded, [?MODULE]), 148 {ok,{InetSucc,FailExp}} = rpc:call(Node, erl_prim_loader, 149 get_modules, [Ms,Process,Path]), 150 SuccExp = lists:sort(InetSucc), 151 152 stop_node(Node), 153 unlink(BootPid), 154 exit(BootPid, kill), 155 156 ok. 157 158get_modules_sorted(Ms, Process, Path) -> 159 case erl_prim_loader:get_modules(Ms, Process, Path) of 160 {ok,{Succ,FailExp}} -> 161 {ok,{lists:sort(Succ),lists:sort(FailExp)}}; 162 Other -> 163 Other 164 end. 165 166normalize_and_backslash(Config) -> 167 %% Test OTP-11170 168 case os:type() of 169 {win32,_} -> 170 {skip, "not on windows"}; 171 _ -> 172 test_normalize_and_backslash(Config) 173 end. 174test_normalize_and_backslash(Config) -> 175 PrivDir = proplists:get_value(priv_dir,Config), 176 Dir = filename:join(PrivDir,"\\"), 177 File = filename:join(Dir,"file-OTP-11170"), 178 ok = file:make_dir(Dir), 179 ok = file:write_file(File,"a file to test OTP-11170"), 180 {ok,["file-OTP-11170"]} = file:list_dir(Dir), 181 {ok,["file-OTP-11170"]} = erl_prim_loader:list_dir(Dir), 182 ok = file:delete(File), 183 ok = file:del_dir(Dir), 184 ok. 185 186%% Start a node using the 'inet' loading method, 187%% from an already started boot server. 188inet_existing(Config) when is_list(Config) -> 189 Name = erl_prim_test_inet_existing, 190 BootPid = start_boot_server(), 191 Node = start_node_using_inet(Name), 192 {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), 193 stop_node(Node), 194 unlink(BootPid), 195 exit(BootPid, kill), 196 ok. 197 198%% Start a node using the 'inet' loading method, 199%% but start the boot server afterwards. 200inet_coming_up(Config) when is_list(Config) -> 201 Name = erl_prim_test_inet_coming_up, 202 Node = start_node_using_inet(Name, [{wait,false}]), 203 204 %% Wait a while, then start boot server, and wait for node to start. 205 ct:sleep({seconds,6}), 206 BootPid = start_boot_server(), 207 wait_really_started(Node, 25), 208 209 %% Check loader argument, then cleanup. 210 {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), 211 stop_node(Node), 212 unlink(BootPid), 213 exit(BootPid, kill), 214 ok. 215 216wait_really_started(Node, 0) -> 217 ct:fail({not_booted,Node}); 218wait_really_started(Node, N) -> 219 case rpc:call(Node, init, get_status, []) of 220 {started, _} -> 221 ok; 222 _ -> 223 ct:sleep(1000), 224 wait_really_started(Node, N - 1) 225 end. 226 227%% Start a node using the 'inet' loading method, 228%% then lose the connection. 229inet_disconnects(Config) when is_list(Config) -> 230 case test_server:is_native(erl_boot_server) of 231 true -> 232 {skip,"erl_boot_server is native"}; 233 false -> 234 Name = erl_prim_test_inet_disconnects, 235 236 BootPid = start_boot_server(), 237 unlink(BootPid), 238 Self = self(), 239 %% This process shuts down the boot server during loading. 240 Stopper = spawn_link(fun() -> stop_boot(BootPid, Self) end), 241 receive 242 {Stopper,ready} -> ok 243 end, 244 245 %% Let the loading begin... 246 Node = start_node_using_inet(Name, [{wait,false}]), 247 248 %% When the stopper is ready, the slave node should be 249 %% looking for a boot server again. 250 receive 251 {Stopper,ok} -> 252 ok; 253 {Stopper,{error,Reason}} -> 254 ct:fail(Reason) 255 after 60000 -> 256 ct:fail(stopper_died) 257 end, 258 259 %% Start new boot server to see that loading is continued. 260 BootPid2 = start_boot_server(), 261 wait_really_started(Node, 25), 262 {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), 263 stop_node(Node), 264 unlink(BootPid2), 265 exit(BootPid2, kill), 266 ok 267 end. 268 269%% Trace boot server calls and stop the server before loading is finished. 270stop_boot(BootPid, Super) -> 271 erlang:trace(all, true, [call]), 272 1 = erlang:trace_pattern({erl_boot_server,send_file_result,3}, true, [local]), 273 BootRef = erlang:monitor(process, BootPid), 274 Super ! {self(),ready}, 275 Result = get_calls(100, BootPid), 276 exit(BootPid, kill), 277 erlang:trace_pattern({erl_boot_server,send_file_result,3}, false, [local]), 278 erlang:trace(all, false, [call]), 279 receive 280 {'DOWN',BootRef,_,_, killed} -> ok 281 end, 282 Super ! {self(),Result}. 283 284get_calls(0, _) -> 285 ok; 286get_calls(Count, Pid) -> 287 receive 288 {trace,_,call,_MFA} -> 289 get_calls(Count-1, Pid) 290 after 10000 -> 291 {error,{trace_msg_timeout,Count}} 292 end. 293 294%% Start nodes in parallel, all using the 'inet' loading method; 295%% verify that the boot server manages. 296multiple_slaves(Config) when is_list(Config) -> 297 Name = erl_prim_test_multiple_slaves, 298 Host = host(), 299 IpStr = ip_str(Host), 300 Args = " -loader inet -hosts " ++ IpStr, 301 302 NoOfNodes = 10, % no of slave nodes to be started 303 304 NamesAndNodes = 305 lists:map(fun(N) -> 306 NameN = atom_to_list(Name) ++ 307 integer_to_list(N), 308 NodeN = NameN ++ "@" ++ Host, 309 {list_to_atom(NameN),list_to_atom(NodeN)} 310 end, lists:seq(1, NoOfNodes)), 311 312 Nodes = start_multiple_nodes(NamesAndNodes, Args, []), 313 314 %% "queue up" the nodes to wait for the boot server to respond 315 %% (note: test_server supervises each node start by accept() 316 %% on a socket, the timeout value for the accept has to be quite 317 %% long for this test to work). 318 ct:sleep({seconds,5}), 319 %% start the code loading circus! 320 BootPid = start_boot_server(), 321 %% give the nodes a chance to boot up before attempting to stop them 322 ct:sleep({seconds,10}), 323 324 wait_and_shutdown(lists:reverse(Nodes), 30), 325 326 unlink(BootPid), 327 exit(BootPid, kill), 328 ok. 329 330start_multiple_nodes([{Name,Node} | NNs], Args, Started) -> 331 {ok,Node} = start_node(Name, Args, [{wait, false}]), 332 start_multiple_nodes(NNs, Args, [Node | Started]); 333start_multiple_nodes([], _, Nodes) -> 334 Nodes. 335 336wait_and_shutdown([Node | Nodes], Tries) -> 337 wait_really_started(Node, Tries), 338 {ok,[["inet"]]} = rpc:call(Node, init, get_argument, [loader]), 339 stop_node(Node), 340 wait_and_shutdown(Nodes, Tries); 341wait_and_shutdown([], _) -> 342 ok. 343 344 345%% Start a node using the 'inet' loading method, 346%% verify that the boot server responds to file requests. 347file_requests(Config) when is_list(Config) -> 348 {ok, Node, BootPid} = complete_start_node(erl_prim_test_file_req), 349 350 %% compare with results from file server calls (the 351 %% boot server uses the same file sys and cwd) 352 {ok,Files} = file:list_dir("."), 353 io:format("Files: ~p~n",[Files]), 354 {ok,Files} = rpc:call(Node, erl_prim_loader, list_dir, ["."]), 355 {ok,Info} = file:read_file_info(code:which(test_server)), 356 {ok,Info} = rpc:call(Node, erl_prim_loader, read_file_info, 357 [code:which(test_server)]), 358 359 PrivDir = proplists:get_value(priv_dir,Config), 360 Dir = filename:join(PrivDir,?MODULE_STRING++"_file_requests"), 361 ok = file:make_dir(Dir), 362 Alias = filename:join(Dir,"symlink"), 363 case file:make_symlink(code:which(test_server), Alias) of 364 {error, enotsup} -> 365 %% Links not supported on this platform 366 ok; 367 {error, eperm} -> 368 {win32,_} = os:type(), 369 %% Windows user not privileged to create symlinks" 370 ok; 371 ok -> 372 %% Reading file info for link should return file info for 373 %% link target 374 {ok,Info} = rpc:call(Node, erl_prim_loader, read_file_info, 375 [Alias]), 376 #file_info{type=regular} = Info, 377 {ok,#file_info{type=symlink}} = 378 rpc:call(Node, erl_prim_loader, read_link_info, 379 [Alias]) 380 end, 381 382 {ok,Cwd} = file:get_cwd(), 383 {ok,Cwd} = rpc:call(Node, erl_prim_loader, get_cwd, []), 384 case file:get_cwd("C:") of 385 {error,enotsup} -> 386 ok; 387 {ok,DCwd} -> 388 {ok,DCwd} = rpc:call(Node, erl_prim_loader, get_cwd, ["C:"]) 389 end, 390 391 stop_node(Node), 392 unlink(BootPid), 393 exit(BootPid, kill), 394 ok. 395 396%% Read files from local archive. 397local_archive(Config) when is_list(Config) -> 398 PrivDir = proplists:get_value(priv_dir, Config), 399 KernelDir = filename:basename(code:lib_dir(kernel)), 400 Archive = filename:join([PrivDir, KernelDir ++ init:archive_extension()]), 401 file:delete(Archive), 402 {ok, Archive} = create_archive(Archive, [KernelDir]), 403 404 Node = node(), 405 BeamName = "inet.beam", 406 ok = test_archive(Node, Archive, KernelDir, BeamName), 407 408 %% Cleanup 409 ok = rpc:call(Node, erl_prim_loader, purge_archive_cache, []), 410 ok = file:delete(Archive), 411 ok. 412 413%% Read files from remote archive. 414remote_archive(Config) when is_list(Config) -> 415 PrivDir = proplists:get_value(priv_dir, Config), 416 KernelDir = filename:basename(code:lib_dir(kernel)), 417 Archive = filename:join([PrivDir, KernelDir ++ init:archive_extension()]), 418 file:delete(Archive), 419 {ok, Archive} = create_archive(Archive, [KernelDir]), 420 421 {ok, Node, BootPid} = complete_start_node(remote_archive), 422 423 BeamName = "inet.beam", 424 ok = test_archive(Node, Archive, KernelDir, BeamName), 425 426 %% Cleanup 427 stop_node(Node), 428 unlink(BootPid), 429 exit(BootPid, kill), 430 ok. 431 432%% Read files from primary archive. 433primary_archive(Config) when is_list(Config) -> 434 %% Copy the orig files to priv_dir 435 PrivDir = proplists:get_value(priv_dir, Config), 436 Archive = filename:join([PrivDir, "primary_archive.zip"]), 437 file:delete(Archive), 438 DataDir = proplists:get_value(data_dir, Config), 439 {ok, _} = zip:create(Archive, ["primary_archive"], 440 [{compress, []}, {cwd, DataDir}]), 441 {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]), 442 TopDir = filename:join([PrivDir, "primary_archive"]), 443 444 %% Compile the code 445 DictDir = "primary_archive_dict-1.0", 446 DummyDir = "primary_archive_dummy", 447 ok = compile_app(TopDir, DictDir), 448 ok = compile_app(TopDir, DummyDir), 449 450 %% Create the archive 451 {ok, TopFiles} = file:list_dir(TopDir), 452 {ok, {_, ArchiveBin}} = zip:create(Archive, TopFiles, 453 [memory, {compress, []}, {cwd, TopDir}]), 454 455 %% Use temporary node to simplify cleanup 456 Cookie = atom_to_list(erlang:get_cookie()), 457 Args = " -setcookie " ++ Cookie, 458 {ok,Node} = start_node(primary_archive, Args), 459 wait_really_started(Node, 25), 460 {_,_,_} = rpc:call(Node, erlang, date, []), 461 462 %% Set primary archive 463 ExpectedEbins = [Archive, DictDir ++ "/ebin", DummyDir ++ "/ebin"], 464 io:format("ExpectedEbins: ~p\n", [ExpectedEbins]), 465 {ok, FileInfo} = ?PRIM_FILE:read_file_info(Archive), 466 {ok, Ebins} = rpc:call(Node, erl_prim_loader, set_primary_archive, 467 [Archive, ArchiveBin, FileInfo, 468 fun escript:parse_file/1]), 469 ExpectedEbins = lists:sort(Ebins), % assert 470 471 {ok, TopFiles2} = rpc:call(Node, erl_prim_loader, list_dir, [Archive]), 472 [DictDir, DummyDir] = lists:sort(TopFiles2), 473 BeamName = "primary_archive_dict_app.beam", 474 ok = test_archive(Node, Archive, DictDir, BeamName), 475 476 %% Cleanup 477 {ok, []} = rpc:call(Node, erl_prim_loader, set_primary_archive, 478 [undefined, undefined, undefined, 479 fun escript:parse_file/1]), 480 stop_node(Node), 481 ok = file:delete(Archive), 482 ok. 483 484test_archive(Node, TopDir, AppDir, BeamName) -> 485 %% List dir 486 io:format("test_archive: ~p\n", [rpc:call(Node, erl_prim_loader, list_dir, [TopDir])]), 487 {ok, TopFiles} = rpc:call(Node, erl_prim_loader, list_dir, [TopDir]), 488 true = lists:member(AppDir, TopFiles), 489 AbsAppDir = TopDir ++ "/" ++ AppDir, 490 {ok, AppFiles} = rpc:call(Node, erl_prim_loader, list_dir, [AbsAppDir]), 491 true = lists:member("ebin", AppFiles), 492 Ebin = AbsAppDir ++ "/ebin", 493 {ok, EbinFiles} = rpc:call(Node, erl_prim_loader, list_dir, [Ebin]), 494 Beam = Ebin ++ "/" ++ BeamName, 495 true = lists:member(BeamName, EbinFiles), 496 error = rpc:call(Node, erl_prim_loader, list_dir, [TopDir ++ "/no_such_file"]), 497 error = rpc:call(Node, erl_prim_loader, list_dir, [TopDir ++ "/ebin/no_such_file"]), 498 499 %% File info 500 {ok, #file_info{type = directory}} = 501 rpc:call(Node, erl_prim_loader, read_file_info, [TopDir]), 502 {ok, #file_info{type = directory}} = 503 rpc:call(Node, erl_prim_loader, read_file_info, [Ebin]), 504 {ok, #file_info{type = regular} = FI} = 505 rpc:call(Node, erl_prim_loader, read_file_info, [Beam]), 506 error = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir ++ "/no_such_file"]), 507 error = rpc:call(Node, erl_prim_loader, read_file_info, [TopDir ++ "/ebin/no_such_file"]), 508 509 %% Get file 510 {ok, Bin, Beam} = rpc:call(Node, erl_prim_loader, get_file, [Beam]), 511 if 512 FI#file_info.size =:= byte_size(Bin) -> ok; 513 true -> exit({FI#file_info.size, byte_size(Bin)}) 514 end, 515 error = rpc:call(Node, erl_prim_loader, get_file, ["/no_such_file"]), 516 error = rpc:call(Node, erl_prim_loader, get_file, ["/ebin/no_such_file"]), 517 ok. 518 519create_archive(Archive, AppDirs) -> 520 LibDir = code:lib_dir(), 521 Opts = [{compress, []}, {cwd, LibDir}], 522 io:format("zip:create(~p,\n\t~p,\n\t~p).\n", [Archive, AppDirs, Opts]), 523 zip:create(Archive, AppDirs, Opts). 524 525 526%% Read virtual directories from archive. 527virtual_dir_in_archive(Config) when is_list(Config) -> 528 PrivDir = proplists:get_value(priv_dir, Config), 529 Data = <<"A little piece of data.">>, 530 ArchiveBase = "archive_with_virtual_dirs", 531 Archive = filename:join([PrivDir, ArchiveBase ++ init:archive_extension()]), 532 FileBase = "a_data_file.beam", 533 EbinBase = "ebin", 534 FileInArchive = filename:join([ArchiveBase, EbinBase, FileBase]), 535 BinFiles = [{FileInArchive, Data}], 536 Opts = [{compress, []}], 537 file:delete(Archive), 538 io:format("zip:create(~p,\n\t~p,\n\t~p).\n", [Archive, BinFiles, Opts]), 539 {ok, Archive} = zip:create(Archive, BinFiles, Opts), 540 541 %% Verify that there is no directories 542 {ok, BinFiles} = zip:unzip(Archive, [memory]), 543 544 FullPath = filename:join([Archive, FileInArchive]), 545 {ok, _} = erl_prim_loader:read_file_info(FullPath), 546 547 %% Read one virtual dir 548 EbinDir = filename:dirname(FullPath), 549 {ok, _} = erl_prim_loader:read_file_info(EbinDir), 550 {ok, [FileBase]} = erl_prim_loader:list_dir(EbinDir), 551 552 %% Read another virtual dir 553 AppDir = filename:dirname(EbinDir), 554 {ok, _} = erl_prim_loader:read_file_info(AppDir), 555 {ok, [EbinBase]} = erl_prim_loader:list_dir(AppDir), 556 557 %% Cleanup 558 ok = erl_prim_loader:purge_archive_cache(), 559 ok = file:delete(Archive), 560 ok. 561 562%%% 563%%% Helper functions. 564%%% 565 566complete_start_node(Name) -> 567 BootPid = start_boot_server(), 568 Node = start_node_using_inet(Name), 569 wait_really_started(Node, 25), 570 {ok, Node, BootPid}. 571 572start_boot_server() -> 573 %% Many linux systems define: 574 %% 127.0.0.1 localhost 575 %% 127.0.1.1 somehostname 576 %% Therefore, to allow the tests to work on those kind of systems, 577 %% also include "localhost" in the list of allowed hosts. 578 579 Hosts = [host(),ip_str("localhost")], 580 {ok,BootPid} = erl_boot_server:start_link(Hosts), 581 BootPid. 582 583start_node_using_inet(Name) -> 584 start_node_using_inet(Name, []). 585 586start_node_using_inet(Name, Opts) -> 587 Host = host(), 588 IpStr = ip_str(Host), 589 Args = " -loader inet -hosts " ++ IpStr, 590 {ok,Node} = start_node(Name, Args, Opts), 591 Node. 592 593 594ip_str({A, B, C, D}) -> 595 lists:concat([A, ".", B, ".", C, ".", D]); 596ip_str(Host) -> 597 {ok,Ip} = inet:getaddr(Host, inet), 598 ip_str(Ip). 599 600start_node(Name, Args) -> 601 start_node(Name, Args, []). 602 603start_node(Name, Args, Opts) -> 604 Opts2 = [{args, Args}|Opts], 605 io:format("test_server:start_node(~p, peer, ~p).\n", 606 [Name, Opts2]), 607 Res = test_server:start_node(Name, peer, Opts2), 608 io:format("start_node -> ~p\n", [Res]), 609 Res. 610 611host() -> 612 {ok,Host} = inet:gethostname(), 613 Host. 614 615stop_node(Node) -> 616 test_server:stop_node(Node). 617 618compile_app(TopDir, AppName) -> 619 AppDir = filename:join([TopDir, AppName]), 620 SrcDir = filename:join([AppDir, "src"]), 621 OutDir = filename:join([AppDir, "ebin"]), 622 {ok, Files} = file:list_dir(SrcDir), 623 compile_files(Files, SrcDir, OutDir). 624 625compile_files([File | Files], SrcDir, OutDir) -> 626 case filename:extension(File) of 627 ".erl" -> 628 AbsFile = filename:join([SrcDir, File]), 629 case compile:file(AbsFile, [{outdir, OutDir}]) of 630 {ok, _Mod} -> 631 compile_files(Files, SrcDir, OutDir); 632 Error -> 633 {compilation_error, AbsFile, OutDir, Error} 634 end; 635 _ -> 636 compile_files(Files, SrcDir, OutDir) 637 end; 638compile_files([], _, _) -> 639 ok. 640 641