1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2007-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: 
   19: -module(escript_SUITE).
   20: -export([
   21: 	all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   22: 	 init_per_group/2,end_per_group/2,
   23: 	 init_per_testcase/2,
   24: 	 end_per_testcase/2,
   25: 	 basic/1,
   26: 	 errors/1,
   27: 	 strange_name/1,
   28: 	 emulator_flags/1,
   29: 	 emulator_flags_no_shebang/1,
   30: 	 module_script/1,
   31: 	 beam_script/1,
   32: 	 archive_script/1,
   33: 	 archive_script_file_access/1,
   34: 	 epp/1,
   35: 	 create_and_extract/1,
   36: 	 foldl/1,
   37: 	 overflow/1,
   38: 	 verify_sections/3,
   39:          unicode/1
   40: 	]).
   41: 
   42: -include_lib("test_server/include/test_server.hrl").
   43: -include_lib("kernel/include/file.hrl").
   44: 
   45: suite() -> [{ct_hooks,[ts_install_cth]}].
   46: 
   47: all() -> 
   48:     [basic, errors, strange_name, emulator_flags,
   49:      emulator_flags_no_shebang,
   50:      module_script, beam_script, archive_script, epp,
   51:      create_and_extract, foldl, overflow,
   52:      archive_script_file_access, unicode].
   53: 
   54: groups() -> 
   55:     [].
   56: 
   57: init_per_suite(Config) ->
   58:     Config.
   59: 
   60: end_per_suite(_Config) ->
   61:     ok.
   62: 
   63: init_per_group(_GroupName, Config) ->
   64:     Config.
   65: 
   66: end_per_group(_GroupName, Config) ->
   67:     Config.
   68: 
   69: init_per_testcase(_Case, Config) ->
   70:     ?line Dog = ?t:timetrap(?t:minutes(5)),
   71:     [{watchdog,Dog}|Config].
   72: 
   73: end_per_testcase(_Case, Config) ->
   74:     Dog = ?config(watchdog, Config),
   75:     test_server:timetrap_cancel(Dog),
   76:     ok.
   77: 
   78: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   79: 
   80: basic(Config) when is_list(Config) ->
   81:     Data = ?config(data_dir, Config),
   82:     Dir = filename:absname(Data),		%Get rid of trailing slash.
   83:     ?line run(Dir, "factorial 5",
   84: 	      <<"factorial 5 = 120\nExitCode:0">>),
   85:     ?line run(Dir, "factorial_compile 10",
   86: 	      <<"factorial 10 = 3628800\nExitCode:0">>),
   87:     ?line run(Dir, "factorial_compile_main 7",
   88: 	      <<"factorial 7 = 5040\nExitCode:0">>),
   89:     ?line run(Dir, "factorial_warning 20",
   90: 	      [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\n"
   91: 			 "factorial 20 = 2432902008176640000\nExitCode:0">>]),
   92:     ?line run_with_opts(Dir, "-s", "factorial_warning",
   93: 	      [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]),
   94:     ?line run_with_opts(Dir, "-s -i", "factorial_warning",
   95: 	      [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]),
   96:     ?line run_with_opts(Dir, "-c -s", "factorial_warning",
   97: 	      [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]),
   98:     ?line run(Dir, "filesize "++filename:join(?config(data_dir, Config),"filesize"),
   99: 	      [data_dir,<<"filesize:11: Warning: function id/1 is unused\n324\nExitCode:0">>]),
  100:     ?line run(Dir, "test_script_name",
  101: 	      [data_dir,<<"test_script_name\nExitCode:0">>]),
  102:     ?line run(Dir, "tail_rec 1000",
  103: 	      [<<"ok\nExitCode:0">>]),
  104: 
  105:     %% We expect the trap_exit flag for the process to be false,
  106:     %% since that is the default state for newly spawned processes.
  107:     ?line run(Dir, "trap_exit",
  108: 	      <<"false\nExitCode:0">>),
  109:     ok.
  110: 
  111: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  112: 
  113: errors(Config) when is_list(Config) ->
  114:     Data = ?config(data_dir, Config),
  115:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  116:     ?line run(Dir, "compile_error",
  117: 	      [data_dir,<<"compile_error:5: syntax error before: '*'\n">>,
  118: 	       data_dir,<<"compile_error:8: syntax error before: blarf\n">>,
  119: 	       <<"escript: There were compilation errors.\nExitCode:127">>]),
  120:     ?line run(Dir, "lint_error",
  121: 	      [data_dir,<<"lint_error:6: function main/1 already defined\n">>,
  122: 	       data_dir,"lint_error:8: variable 'ExitCode' is unbound\n",
  123: 	       <<"escript: There were compilation errors.\nExitCode:127">>]),
  124:     ?line run_with_opts(Dir, "-s", "lint_error",
  125: 	      [data_dir,<<"lint_error:6: function main/1 already defined\n">>,
  126: 	       data_dir,"lint_error:8: variable 'ExitCode' is unbound\n",
  127: 	       <<"escript: There were compilation errors.\nExitCode:127">>]),
  128:     ok.
  129: 
  130: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  131: 
  132: strange_name(Config) when is_list(Config) ->
  133:     Data = ?config(data_dir, Config),
  134:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  135:     ?line run(Dir, "strange.name -arg1 arg2 arg3",
  136: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  137: 		"ExitCode:0">>]),
  138:     ok.
  139: 
  140: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  141: 
  142: emulator_flags(Config) when is_list(Config) ->
  143:     Data = ?config(data_dir, Config),
  144:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  145:     ?line run(Dir, "emulator_flags -arg1 arg2 arg3",
  146: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  147: 		"nostick:[{nostick,[]}]\n"
  148: 		"mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
  149: 		"ERL_FLAGS=false\n"
  150: 		"unknown:[]\n"
  151: 		"ExitCode:0">>]),
  152:     ok.
  153: 
  154: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  155: 
  156: emulator_flags_no_shebang(Config) when is_list(Config) ->
  157:     Data = ?config(data_dir, Config),
  158:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  159:     %% Need run_with_opts, to always use "escript" explicitly
  160:     ?line run_with_opts(Dir, "", "emulator_flags_no_shebang -arg1 arg2 arg3",
  161: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  162: 		"nostick:[{nostick,[]}]\n"
  163: 		"mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
  164: 		"ERL_FLAGS=false\n"
  165: 		"unknown:[]\n"
  166: 		"ExitCode:0">>]),
  167:     ok.
  168: 
  169: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  170: %% Pick the source code from the emulator_flags script
  171: %% Generate a new escript with a module header
  172: 
  173: module_script(Config) when is_list(Config) ->
  174:     %% Read orig file
  175:     Data = ?config(data_dir, Config),
  176:     OrigFile = filename:join([Data,"emulator_flags"]),
  177:     {ok, OrigBin} = file:read_file(OrigFile),
  178:     ?line [Shebang, Mode, Flags | Source] = string:tokens(binary_to_list(OrigBin), "\n"),
  179:     ?line {ok, OrigFI} = file:read_file_info(OrigFile),
  180: 
  181:     %% Write source file
  182:     Priv = ?config(priv_dir, Config),
  183:     Dir = filename:absname(Priv), % Get rid of trailing slash.
  184:     Base = "module_script",
  185:     ErlFile = filename:join([Priv, Base ++ ".erl"]),
  186:     ErlCode = ["\n-module(", Base, ").\n",
  187: 	       "-export([main/1]).\n\n",
  188: 	       string:join(Source, "\n"),
  189: 	       "\n"],
  190:     ?line ok = file:write_file(ErlFile, ErlCode),
  191: 
  192:     %%%%%%%
  193:     %% Create and run scripts without emulator flags
  194: 
  195:     %% With shebang
  196:     NoArgsBase = Base ++ "_no_args_with_shebang",
  197:     NoArgsFile = filename:join([Priv, NoArgsBase]),
  198:     ?line ok = file:write_file(NoArgsFile,
  199: 			       [Shebang, "\n",
  200: 				ErlCode]),
  201:     ?line ok = file:write_file_info(NoArgsFile, OrigFI),
  202: 
  203:     ?line run(Dir, NoArgsBase ++ " -arg1 arg2 arg3",
  204: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  205: 		"nostick:[]\n"
  206: 		"mnesia:[]\n"
  207: 		"ERL_FLAGS=false\n"
  208: 		"unknown:[]\n"
  209: 		"ExitCode:0">>]),
  210: 
  211:     ?line run_with_opts(Dir, "", NoArgsBase ++  " -arg1 arg2 arg3",
  212: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  213: 		"nostick:[]\n"
  214: 		"mnesia:[]\n"
  215: 		"ERL_FLAGS=false\n"
  216: 		"unknown:[]\n"
  217: 		"ExitCode:0">>]),
  218: 
  219:     %% Without shebang
  220:     NoArgsBase2 = Base ++ "_no_args_without_shebang",
  221:     NoArgsFile2 = filename:join([Priv, NoArgsBase2]),
  222:     ?line ok = file:write_file(NoArgsFile2,
  223: 			       ["Something else than shebang!!!", "\n",
  224: 				ErlCode]),
  225:     ?line ok = file:write_file_info(NoArgsFile2, OrigFI),
  226: 
  227:     ?line run_with_opts(Dir, "", NoArgsBase2 ++  " -arg1 arg2 arg3",
  228: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  229: 		"nostick:[]\n"
  230: 		"mnesia:[]\n"
  231: 		"ERL_FLAGS=false\n"
  232: 		"unknown:[]\n"
  233: 		"ExitCode:0">>]),
  234: 
  235:     %% Plain module without header
  236:     NoArgsBase3 = Base ++ "_no_args_without_header",
  237:     NoArgsFile3 = filename:join([Priv, NoArgsBase3]),
  238:     ?line ok = file:write_file(NoArgsFile3, [ErlCode]),
  239:     ?line ok = file:write_file_info(NoArgsFile3, OrigFI),
  240: 
  241:     ?line run_with_opts(Dir, "", NoArgsBase3 ++  " -arg1 arg2 arg3",
  242: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  243: 		"nostick:[]\n"
  244: 		"mnesia:[]\n"
  245: 		"ERL_FLAGS=false\n"
  246: 		"unknown:[]\n"
  247: 		"ExitCode:0">>]),
  248: 
  249:     %%%%%%%
  250:     %% Create and run scripts with emulator flags
  251: 
  252:     %% With shebang
  253:     ArgsBase = Base ++ "_args_with_shebang",
  254:     ArgsFile = filename:join([Priv, ArgsBase]),
  255:     ?line ok = file:write_file(ArgsFile,
  256: 			       [Shebang, "\n",
  257: 				Mode, "\n",
  258: 				Flags, "\n",
  259: 				ErlCode]),
  260:     ?line ok = file:write_file_info(ArgsFile, OrigFI),
  261: 
  262:     ?line run(Dir, ArgsBase ++  " -arg1 arg2 arg3",
  263: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  264: 		"nostick:[{nostick,[]}]\n"
  265: 		"mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
  266: 		"ERL_FLAGS=false\n"
  267: 		"unknown:[]\n"
  268: 		"ExitCode:0">>]),
  269: 
  270:     ok.
  271: 
  272: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  273: %% Pick the source code from the emulator_flags script and compile it.
  274: %% Generate a new escript containing the beam code and the escript header
  275: beam_script(Config) when is_list(Config) ->
  276:     %% Read orig file
  277:     Data = ?config(data_dir, Config),
  278:     OrigFile = filename:join([Data,"emulator_flags"]),
  279:     {ok, OrigBin} = file:read_file(OrigFile),
  280:     ?line [Shebang, Mode, Flags | Source] = string:tokens(binary_to_list(OrigBin), "\n"),
  281:     ?line {ok, OrigFI} = file:read_file_info(OrigFile),
  282: 
  283:     %% Write source file
  284:     Priv = ?config(priv_dir, Config),
  285:     Dir = filename:absname(Priv), % Get rid of trailing slash.
  286:     Base = "beam_script",
  287:     ErlFile = filename:join([Priv, Base ++ ".erl"]),
  288:     ?line ok = file:write_file(ErlFile,
  289: 			       ["\n-module(", Base, ").\n",
  290: 				"-export([main/1]).\n\n",
  291: 				string:join(Source, "\n"),
  292: 				"\n"]),
  293: 
  294:     %% Compile the code
  295:     ?line {ok, _Mod, BeamCode} = compile:file(ErlFile, [binary]),
  296: 
  297:     %%%%%%%
  298:     %% Create and run scripts without emulator flags
  299: 
  300:     %% With shebang
  301:     NoArgsBase = Base ++ "_no_args_with_shebang",
  302:     NoArgsFile = filename:join([Priv, NoArgsBase]),
  303:     ?line ok = file:write_file(NoArgsFile,
  304: 			       [Shebang, "\n",
  305: 				BeamCode]),
  306:     ?line ok = file:write_file_info(NoArgsFile, OrigFI),
  307: 
  308:     ?line run(Dir, NoArgsBase ++  " -arg1 arg2 arg3",
  309: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  310: 		"nostick:[]\n"
  311: 		"mnesia:[]\n"
  312: 		"ERL_FLAGS=false\n"
  313: 		"unknown:[]\n"
  314: 		"ExitCode:0">>]),
  315: 
  316:     ?line run_with_opts(Dir, "", NoArgsBase ++  " -arg1 arg2 arg3",
  317: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  318: 		"nostick:[]\n"
  319: 		"mnesia:[]\n"
  320: 		"ERL_FLAGS=false\n"
  321: 		"unknown:[]\n"
  322: 		"ExitCode:0">>]),
  323: 
  324:     %% Without shebang
  325:     NoArgsBase2 = Base ++ "_no_args_without_shebang",
  326:     NoArgsFile2 = filename:join([Priv, NoArgsBase2]),
  327:     ?line ok = file:write_file(NoArgsFile2,
  328: 			       ["Something else than shebang!!!", "\n",
  329: 				BeamCode]),
  330:     ?line ok = file:write_file_info(NoArgsFile2, OrigFI),
  331: 
  332:     ?line run_with_opts(Dir, "", NoArgsBase2 ++  " -arg1 arg2 arg3",
  333: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  334: 		"nostick:[]\n"
  335: 		"mnesia:[]\n"
  336: 		"ERL_FLAGS=false\n"
  337: 		"unknown:[]\n"
  338: 		"ExitCode:0">>]),
  339: 
  340:     %% Plain beam file without header
  341:     NoArgsBase3 = Base ++ "_no_args_without_header",
  342:     NoArgsFile3 = filename:join([Priv, NoArgsBase3]),
  343:     ?line ok = file:write_file(NoArgsFile3, [BeamCode]),
  344:     ?line ok = file:write_file_info(NoArgsFile3, OrigFI),
  345: 
  346:     ?line run_with_opts(Dir, "", NoArgsBase3 ++  " -arg1 arg2 arg3",
  347: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  348: 		"nostick:[]\n"
  349: 		"mnesia:[]\n"
  350: 		"ERL_FLAGS=false\n"
  351: 		"unknown:[]\n"
  352: 		"ExitCode:0">>]),
  353: 
  354:     %%%%%%%
  355:     %% Create and run scripts with emulator flags
  356: 
  357:     %% With shebang
  358:     ArgsBase = Base ++ "_args",
  359:     ArgsFile = filename:join([Priv, ArgsBase]),
  360:     ?line ok = file:write_file(ArgsFile,
  361: 			       [Shebang, "\n",
  362: 				Mode, "\n",
  363: 				Flags, "\n",
  364: 				BeamCode]),
  365:     ?line ok = file:write_file_info(ArgsFile, OrigFI),
  366: 
  367:     ?line run(Dir, ArgsBase ++  " -arg1 arg2 arg3",
  368: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  369: 		"nostick:[{nostick,[]}]\n"
  370: 		"mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
  371: 		"ERL_FLAGS=false\n"
  372: 		"unknown:[]\n"
  373: 		"ExitCode:0">>]),
  374:     ok.
  375: 
  376: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  377: %% Create an archive file containing two entire applications plus two
  378: %% alternate main modules. Generate a new escript containing the archive
  379: %% (with .app and .beam files and) and the escript header.
  380: 
  381: archive_script(Config) when is_list(Config) ->
  382:     %% Copy the orig files to priv_dir
  383:     DataDir = ?config(data_dir, Config),
  384:     PrivDir = ?config(priv_dir, Config),
  385:     Archive = filename:join([PrivDir, "archive_script.zip"]),
  386:     ?line {ok, _} = zip:create(Archive, ["archive_script"],
  387: 			       [{compress, []}, {cwd, DataDir}]),
  388:     ?line {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]),
  389:     TopDir = filename:join([PrivDir, "archive_script"]),
  390: 
  391:     %% Compile the code
  392:     ?line ok = compile_app(TopDir, "archive_script_dict"),
  393:     ?line ok = compile_app(TopDir, "archive_script_dummy"),
  394:     ?line {ok, MainFiles} = file:list_dir(TopDir),
  395:     ?line ok = compile_files(MainFiles, TopDir, TopDir),
  396: 
  397:     %% Create the archive
  398:     {ok, TopFiles} = file:list_dir(TopDir),
  399:     ?line {ok, {_, ArchiveBin}} = zip:create(Archive, TopFiles,
  400: 					     [memory, {compress, []}, {cwd, TopDir}]),
  401: 
  402:     %% Read the source script
  403:     OrigFile = filename:join([DataDir, "emulator_flags"]),
  404:     {ok, OrigBin} = file:read_file(OrigFile),
  405:     ?line [Shebang, Mode, _Flags | _Source] =
  406: 	string:tokens(binary_to_list(OrigBin), "\n"),
  407:     Flags = "%%! -archive_script_dict foo bar"
  408: 	" -archive_script_dict foo"
  409: 	" -archive_script_dummy bar",
  410:     ?line {ok, OrigFI} = file:read_file_info(OrigFile),
  411: 
  412:     %%%%%%%
  413:     %% Create and run scripts without emulator flags
  414:     MainBase = "archive_script_main",
  415:     MainScript = filename:join([PrivDir, MainBase]),
  416: 
  417:     %% With shebang
  418:     ?line ok = file:write_file(MainScript,
  419: 			       [Shebang, "\n",
  420: 				Flags, "\n",
  421: 				ArchiveBin]),
  422:     ?line ok = file:write_file_info(MainScript, OrigFI),
  423: 
  424:     ?line run(PrivDir, MainBase ++  " -arg1 arg2 arg3",
  425: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  426: 		"dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n"
  427: 		"dummy:[{archive_script_dummy,[\"bar\"]}]\n"
  428: 		"priv:{ok,<<\"Some private data...\\n\">>}\n"
  429: 		"ExitCode:0">>]),
  430: 
  431:     ?line run_with_opts(PrivDir, "", MainBase ++  " -arg1 arg2 arg3",
  432: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  433: 		"dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n"
  434: 		"dummy:[{archive_script_dummy,[\"bar\"]}]\n"
  435: 		"priv:{ok,<<\"Some private data...\\n\">>}\n"
  436: 		"ExitCode:0">>]),
  437: 
  438:     ?line ok = file:rename(MainScript, MainScript ++ "_with_shebang"),
  439: 
  440:     %% Without shebang (no flags)
  441:     ?line ok = file:write_file(MainScript,
  442: 			       ["Something else than shebang!!!", "\n",
  443: 				ArchiveBin]),
  444:     ?line ok = file:write_file_info(MainScript, OrigFI),
  445: 
  446:     ?line run_with_opts(PrivDir, "", MainBase ++  " -arg1 arg2 arg3",
  447: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  448: 		"dict:[]\n"
  449: 		"dummy:[]\n"
  450: 		"priv:{ok,<<\"Some private data...\\n\">>}\n"
  451: 		"ExitCode:0">>]),
  452:     ?line ok = file:rename(MainScript, MainScript ++ "_without_shebang"),
  453: 
  454:     %% Plain archive without header (no flags)
  455: 
  456:     ?line ok = file:write_file(MainScript, [ArchiveBin]),
  457:     ?line ok = file:write_file_info(MainScript, OrigFI),
  458: 
  459:     ?line run_with_opts(PrivDir, "", MainBase ++  " -arg1 arg2 arg3",
  460: 	      [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  461: 		"dict:[]\n"
  462: 		"dummy:[]\n"
  463: 		"priv:{ok,<<\"Some private data...\\n\">>}\n"
  464: 		"ExitCode:0">>]),
  465:     ?line ok = file:rename(MainScript, MainScript ++ "_without_header"),
  466: 
  467:     %%%%%%%
  468:     %% Create and run scripts with emulator flags
  469:     AltBase = "archive_script_alternate_main",
  470:     AltScript = filename:join([PrivDir, AltBase]),
  471:     ?line ok = file:write_file(AltScript,
  472: 			       [Shebang, "\n",
  473: 				Mode, "\n",
  474: 				Flags, " -escript main archive_script_main2\n",
  475: 				ArchiveBin]),
  476:     ?line ok = file:write_file_info(AltScript, OrigFI),
  477: 
  478:     ?line run(PrivDir, AltBase ++  " -arg1 arg2 arg3",
  479: 	      [<<"main2:[\"-arg1\",\"arg2\",\"arg3\"]\n"
  480: 		"dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n"
  481: 		"dummy:[{archive_script_dummy,[\"bar\"]}]\n"
  482: 		"priv:{ok,<<\"Some private data...\\n\">>}\n"
  483: 		"ExitCode:0">>]),
  484: 
  485:     ok.
  486: 
  487: %% Test the correction of OTP-10071
  488: %% The errors identified are
  489: %%
  490: %% a) If primary archive was named "xxx", then a file in the same
  491: %%    directory named "xxxyyy" would be interpreted as a file named yyy
  492: %%    inside the archive.
  493: %%
  494: %% b) erl_prim_loader did not correctly create and normalize absolute
  495: %%    paths for primary archive and files inside it, so unless given
  496: %%    with exact same path files inside the archive would not be
  497: %%    found. E.g. if escript was started as ./xxx then "xxx/file"
  498: %%    would not be found since erl_prim_loader would try to match
  499: %%    /full/path/to/xxx with /full/path/to/./xxx. Same problem with
  500: %%    ../. Also, the use of symlinks in the path to the archive would
  501: %%    cause problems.
  502: %%
  503: %% c) Depending on how the primary archive was built,
  504: %%    erl_prim_loader:list_dir/1 would sometimes return an empty string
  505: %%    inside the file list. This was a virtual element representing the
  506: %%    top directory of the archive. This shall not occur.
  507: %%
  508: archive_script_file_access(Config) when is_list(Config) ->
  509:     %% Copy the orig files to priv_dir
  510:     DataDir = ?config(data_dir, Config),
  511:     PrivDir = ?config(priv_dir, Config),
  512: 
  513:     MainMod = "archive_script_file_access",
  514:     MainSrc = MainMod ++ ".erl",
  515:     MainBeam = MainMod ++ ".beam",
  516: 
  517:     Archive = filename:join([PrivDir, "archive_script_file_access.zip"]),
  518:     ?line {ok, _} = zip:create(Archive, ["archive_script_file_access"],
  519: 			       [{compress, []}, {cwd, DataDir}]),
  520:     ?line {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]),
  521:     TopDir = filename:join([PrivDir, "archive_script_file_access"]),
  522: 
  523:     %% Compile the code
  524:     ?line ok = compile_files([MainSrc], TopDir, TopDir),
  525: 
  526:     %% First, create a file structure which will be included in the archive:
  527:     %%
  528:     %% dir1/
  529:     %% dir1/subdir1/
  530:     %% dir1/subdir1/file1
  531:     %%
  532:     {ok, OldDir} = file:get_cwd(),
  533:     ok = file:set_cwd(TopDir),
  534:     DummyDir = "dir1",
  535:     DummySubDir = filename:join(DummyDir, "subdir1"),
  536:     RelDummyFile = filename:join(DummySubDir, "file1"),
  537:     DummyFile = filename:join(TopDir,RelDummyFile),
  538:     ok = filelib:ensure_dir(DummyFile),
  539:     ok = file:write_file(DummyFile, ["foo\nbar\nbaz"]),
  540: 
  541:     %% 1. Create zip archive by adding the dummy file and the beam
  542:     %%    file as binaries to zip.
  543:     %%
  544:     %% This used to provoke the following issues when the script was run as
  545:     %% "./<script_name>":
  546:     %% a. erl_prim_loader:read_file_info/1 returning 'error'
  547:     %% b. erl_prim_loader:list_dir/1 returning {ok, ["dir1", [], "file1"]}
  548:     %%    leading to an infinite loop in reltool_target:spec_dir/1
  549:     Files1 =
  550: 	lists:map(fun(Filename) ->
  551: 			  {ok, Bin} = file:read_file(Filename),
  552: 			  {Filename,Bin}
  553: 		  end,
  554: 		  [RelDummyFile,MainBeam]),
  555:     {ok, {"mem", Bin1}} = zip:create("mem", Files1, [memory]),
  556: 
  557:     %% Create the escript
  558:     ScriptName1 = "archive_script_file_access1",
  559:     Script1 = filename:join([PrivDir, ScriptName1]),
  560:     Flags = "-escript main " ++ MainMod,
  561:     ok = escript:create(Script1,[shebang,{emu_args,Flags},{archive,Bin1}]),
  562:     ok = file:change_mode(Script1,8#00744),
  563: 
  564:     %% If supported, create a symlink to the script. This is used to
  565:     %% test error b) described above this test case.
  566:     SymlinkName1 = "symlink_to_"++ScriptName1,
  567:     Symlink1 = filename:join([PrivDir, SymlinkName1]),
  568:     file:make_symlink(ScriptName1,Symlink1), % will fail if not supported
  569: 
  570:     %% Also add a dummy file in the same directory with the same name
  571:     %% as the script except is also has an extension. This used to
  572:     %% test error a) described above this test case.
  573:     ok = file:write_file(Script1 ++ ".extension",
  574: 			 <<"same name as script, but with extension">>),
  575: 
  576:     %% Change to script's directory and run it as "./<script_name>"
  577:     ok = file:set_cwd(PrivDir),
  578:     run(PrivDir, "./" ++ ScriptName1 ++ " " ++ ScriptName1,
  579: 	[<<"ExitCode:0">>]),
  580:     ok = file:set_cwd(TopDir),
  581: 
  582: 
  583:     %% 2. Create zip archive by letting zip read the files from the file system
  584:     %%
  585:     %% The difference compared to the archive_script_file_access1 is
  586:     %% that this will have a file element for each directory in the
  587:     %% archive - while archive_script_file_access1 will only have a
  588:     %% file element per regular file.
  589:     Files2 = [DummyDir,MainBeam],
  590:     {ok, {"mem", Bin2}} = zip:create("mem", Files2, [memory]),
  591: 
  592:     %% Create the escript
  593:     ScriptName2 = "archive_script_file_access2",
  594:     Script2 = filename:join([PrivDir, ScriptName2]),
  595:     ok = escript:create(Script2,[shebang,{emu_args,Flags},{archive,Bin2}]),
  596:     ok = file:change_mode(Script2,8#00744),
  597: 
  598:     %% Also add a dummy file in the same directory with the same name
  599:     %% as the script except is also has an extension. This used to
  600:     %% test error a) described above this test case.
  601:     ok = file:write_file(Script2 ++ ".extension",
  602: 			 <<"same name as script, but with extension">>),
  603: 
  604:     %% If supported, create a symlink to the script. This is used to
  605:     %% test error b) described above this test case.
  606:     SymlinkName2 = "symlink_to_"++ScriptName2,
  607:     Symlink2 = filename:join([PrivDir, SymlinkName2]),
  608:     file:make_symlink(ScriptName2,Symlink2), % will fail if not supported
  609: 
  610:     %% Change to script's directory and run it as "./<script_name>"
  611:     ok = file:set_cwd(PrivDir),
  612:     run(PrivDir, "./" ++ ScriptName2 ++ " " ++ ScriptName2,
  613: 	[<<"ExitCode:0">>]),
  614: 
  615:     %% 3. If symlinks are supported, run one of the scripts via a symlink.
  616:     %%
  617:     %% This is in order to test error b) described above this test case.
  618:     case element(1,os:type()) =:= win32 orelse file:read_link(Symlink2) of
  619: 	{ok,_} ->
  620: 	    run(PrivDir, "./" ++ SymlinkName2 ++ " " ++ ScriptName2,
  621: 		[<<"ExitCode:0">>]);
  622: 	_ -> % not supported
  623: 	    ok
  624:     end,
  625:     ok = file:set_cwd(OldDir).
  626: 
  627: 
  628: compile_app(TopDir, AppName) ->
  629:     AppDir = filename:join([TopDir, AppName]),
  630:     SrcDir = filename:join([AppDir, "src"]),
  631:     OutDir = filename:join([AppDir, "ebin"]),
  632:     ?line {ok, Files} = file:list_dir(SrcDir),
  633:     compile_files(Files, SrcDir, OutDir).
  634: 
  635: compile_files([File | Files], SrcDir, OutDir) ->
  636:     case filename:extension(File) of
  637: 	".erl" ->
  638: 	    AbsFile = filename:join([SrcDir, File]),
  639: 	    case compile:file(AbsFile, [{outdir, OutDir},report_errors]) of
  640: 		{ok, _Mod} ->
  641: 		    compile_files(Files, SrcDir, OutDir);
  642: 		Error ->
  643: 		    {compilation_error, AbsFile, OutDir, Error}
  644: 	    end;
  645: 	_ ->
  646: 	    compile_files(Files, SrcDir, OutDir)
  647:     end;
  648: compile_files([], _, _) ->
  649:     ok.
  650: 
  651: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  652: 
  653: epp(Config) when is_list(Config) ->
  654:     Data = ?config(data_dir, Config),
  655:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  656:     ?line run(Dir, "factorial_epp 5",
  657: 	      <<"factorial 5 = 120\nExitCode:0">>),
  658:     ok.
  659: 
  660: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  661: 
  662: create_and_extract(Config) when is_list(Config) ->
  663:     {NewFile, FileInfo,
  664:      EmuArg, Source,
  665:      _ErlBase, ErlCode,
  666:      _BeamBase, BeamCode,
  667:      ArchiveBin} =
  668: 	prepare_creation("create_and_extract", Config),
  669: 
  670:     Bodies =
  671: 	[[{source, ErlCode}],
  672: 	 [{beam, BeamCode}],
  673: 	 [{archive, ArchiveBin}]],
  674: 
  675:     %% Verify all combinations of scripts with shebangs
  676:     [verify_sections(NewFile, FileInfo, S ++ C ++ E ++ B) ||
  677: 	S <- [[{shebang, default}],
  678: 	      [{shebang, "/usr/bin/env     escript"}]],
  679: 	C <- [[],
  680: 	      [{comment, undefined}],
  681: 	      [{comment, default}],
  682: 	      [{comment, "This is a nonsense comment"}]],
  683: 	E <- [[],
  684: 	      [{emu_args, undefined}],
  685: 	      [{emu_args, EmuArg}]],
  686: 	B <- [[{source, Source}] | Bodies]],
  687: 
  688:     %% Verify all combinations of scripts without shebangs
  689:     [verify_sections(NewFile, FileInfo, S ++ C ++ E ++ B) ||
  690: 	S <- [[], [{shebang, undefined}]],
  691: 	C <- [[], [{comment, undefined}]],
  692: 	E <- [[], [{emu_args, undefined}]],
  693: 	B <- Bodies],
  694: 
  695:     %% Verify the compile_source option
  696:     file:delete(NewFile),
  697:     ?line ok = escript:create(NewFile, [{source, Source}]),
  698:     ?line {ok, [_, _, _, {source, Source}]} = escript:extract(NewFile, []),
  699:     ?line {ok, [_, _, _, {source, BeamCode2}]} =
  700: 	escript:extract(NewFile, [compile_source]),
  701:     verify_sections(NewFile, FileInfo,
  702: 		    [{shebang, default},
  703: 		     {comment, default},
  704: 		     {beam, BeamCode2}]),
  705: 
  706:     file:delete(NewFile),
  707:     ok.
  708: 
  709: prepare_creation(Base, Config) ->
  710:     %% Read the source
  711:     PrivDir = ?config(priv_dir, Config),
  712:     DataDir = ?config(data_dir, Config),
  713:     OrigFile = filename:join([DataDir,"emulator_flags"]),
  714:     ?line {ok, FileInfo} = file:read_file_info(OrigFile),
  715:     NewFile = filename:join([PrivDir, Base]),
  716:     ?line {ok, [{shebang, default},
  717: 		{comment, _},
  718: 		{emu_args, EmuArg},
  719: 		{source, Source}]} =
  720: 	escript:extract(OrigFile, []),
  721: 
  722:     %% Compile the code
  723:     ErlFile = NewFile ++ ".erl",
  724:     ErlCode = list_to_binary(["\n-module(", Base, ").\n",
  725: 			      "-export([main/1]).\n\n",
  726: 			      Source, "\n\n"]),
  727:     ?line ok = file:write_file(ErlFile, ErlCode),
  728: 
  729:     %% Compile the code
  730:     ?line {ok, _Mod, BeamCode} =
  731: 	compile:file(ErlFile, [binary, debug_info]),
  732: 
  733:     %% Create an archive
  734:     ?line {ok, {_, ArchiveBin}} =
  735: 	zip:create("dummy_archive_name",
  736: 		   [{Base ++ ".erl", ErlCode},
  737: 		    {Base ++ ".beam", BeamCode}],
  738: 		   [{compress, []}, memory]),
  739:     {NewFile, FileInfo,
  740:      EmuArg, Source,
  741:      Base ++ ".erl", ErlCode,
  742:      Base ++ ".beam", BeamCode,
  743:      ArchiveBin}.
  744: 
  745: verify_sections(File, FileInfo, Sections) ->
  746:     io:format("~p:verify_sections(\n\t~p,\n\t~p,\n\t~p).\n",
  747: 	      [?MODULE, File, FileInfo, Sections]),
  748: 
  749:     %% Create
  750:     file:delete(File),
  751:     ?line ok = escript:create(File, Sections),
  752:     ?line ok = file:write_file_info(File, FileInfo),
  753: 
  754:     %% Run
  755:     Dir = filename:absname(filename:dirname(File)),
  756:     Base = filename:basename(File),
  757: 
  758:     HasArg = fun(Tag) ->
  759: 		     case lists:keysearch(Tag, 1, Sections) of
  760: 			 false -> false;
  761: 			 {value, {_, undefined}} -> false;
  762: 			 {value, _} -> true
  763: 		     end
  764: 	     end,
  765:     ExpectedMain = <<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n">>,
  766:     ExpectedOutput =
  767: 	case HasArg(emu_args) of
  768: 	    true ->
  769: 		<<"nostick:[{nostick,[]}]\n"
  770: 		  "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
  771: 		  "ERL_FLAGS=false\n"
  772: 		  "unknown:[]\n"
  773: 		  "ExitCode:0">>;
  774: 	    false ->
  775: 		<<"nostick:[]\nmnesia:[]\nERL_FLAGS=false\nunknown:[]\nExitCode:0">>
  776: 	end,
  777: 
  778:     InputArgs = Base ++ " -arg1 arg2 arg3",
  779:     Expected = <<ExpectedMain/binary, ExpectedOutput/binary>>,
  780:     case HasArg(shebang) of
  781: 	true ->
  782: 	    ?line run(Dir, InputArgs, [Expected]);
  783: 	false ->
  784: 	    ?line run_with_opts(Dir, [], InputArgs, [Expected])
  785:     end,
  786: 
  787:     %% Verify
  788:     ?line {ok, Bin} = escript:create(binary, Sections),
  789:     ?line {ok, Read} = file:read_file(File),
  790:     ?line Bin = Read, % Assert
  791: 
  792:     Normalized = normalize_sections(Sections),
  793:     ?line {ok, Extracted} = escript:extract(File, []),
  794:     io:format("Normalized; ~p\n", [Normalized]),
  795:     io:format("Extracted ; ~p\n", [Extracted]),
  796:     ?line Normalized = Extracted, % Assert
  797:     ok.
  798: 
  799: normalize_sections(Sections) ->
  800:     AtomToTuple =
  801: 	fun(Val) ->
  802: 		if
  803: 		    is_atom(Val) -> {Val, default};
  804: 		    true -> Val
  805: 		end
  806: 	end,
  807:     case lists:map(AtomToTuple, [{K, V} || {K, V} <- Sections, V =/= undefined]) of
  808: 	    [{shebang, Shebang} | Rest] ->
  809: 		[{shebang, Shebang} |
  810: 		 case Rest of
  811: 		     [{comment, Comment} | Rest2] ->
  812: 			 [{comment, Comment} |
  813: 			  case Rest2 of
  814: 			      [{emu_args, EmuArgs}, Body] ->
  815: 				  [{emu_args, EmuArgs}, Body];
  816: 			      [Body] ->
  817: 				  [{emu_args, undefined}, Body]
  818: 			  end
  819: 			 ];
  820: 		     [{emu_args, EmuArgs}, Body] ->
  821: 			 [{comment, undefined}, {emu_args, EmuArgs}, Body];
  822: 		     [Body] ->
  823: 			 [{comment, undefined}, {emu_args, undefined}, Body]
  824: 		 end
  825: 		];
  826: 	    [Body] ->
  827: 		[{shebang, undefined}, {comment, undefined}, {emu_args, undefined}, Body]
  828: 	end.
  829: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  830: 
  831: 
  832: 
  833: foldl(Config) when is_list(Config) ->
  834:     {NewFile, _FileInfo,
  835:      _EmuArg, _Source,
  836:      ErlBase, ErlCode,
  837:      BeamBase, _BeamCode,
  838:      ArchiveBin} =
  839: 	prepare_creation("foldl", Config),
  840: 
  841:     Collect = fun(Name, GetInfo, GetBin, Acc) ->
  842: 		      [{Name, GetInfo(), GetBin()} | Acc]
  843: 	      end,
  844: 
  845:     %% Get line numbers and the file attribute right
  846:     SourceFile = NewFile ++ ".erl",
  847:     <<_:1/binary, ErlCode2/binary>> = ErlCode,
  848:     ?line ok = file:write_file(SourceFile, ErlCode2),
  849:     ?line {ok, _Mod, BeamCode} =
  850: 	compile:file(SourceFile, [binary, debug_info]),
  851: 
  852:     %% Verify source script
  853:     ?line ok = escript:create(SourceFile, [{source, ErlCode}]),
  854:     ?line {ok, [{".", _, BeamCode2}]}
  855: 	= escript_foldl(Collect, [], SourceFile),
  856: 
  857:     ?line {ok, Abstr} = beam_lib:chunks(BeamCode, [abstract_code]),
  858:     ?line {ok, Abstr2} = beam_lib:chunks(BeamCode2, [abstract_code]),
  859:     %% io:format("abstr1=~p\n", [Abstr]),
  860:     %% io:format("abstr2=~p\n", [Abstr2]),
  861:     ?line Abstr = Abstr2, % Assert
  862: 
  863:     %% Verify beam script
  864:     ?line ok = escript:create(NewFile, [{beam, BeamCode}]),
  865:     ?line {ok, [{".", _, BeamCode}]}
  866: 	= escript_foldl(Collect, [], NewFile),
  867: 
  868:     %% Verify archive scripts
  869:     ?line ok = escript:create(NewFile, [{archive, ArchiveBin}]),
  870:     ?line {ok, [{BeamBase, #file_info{}, _},
  871: 		{ErlBase, #file_info{}, _}]}
  872: 	= escript_foldl(Collect, [], NewFile),
  873: 
  874:     ArchiveFiles = [{ErlBase, ErlCode}, {BeamBase, BeamCode}],
  875:     ?line ok = escript:create(NewFile, [{archive, ArchiveFiles, []}]),
  876:     ?line {ok, [{BeamBase, _, _},
  877: 	      {ErlBase, _, _}]}
  878: 	= escript_foldl(Collect, [], NewFile),
  879: 
  880:     ok.
  881: 
  882: escript_foldl(Fun, Acc, File) ->
  883:     code:ensure_loaded(zip),
  884:     case erlang:function_exported(zip, foldl, 3) of
  885: 	true ->
  886: 	    emulate_escript_foldl(Fun, Acc, File);
  887: 	false ->
  888: 	    escript:foldl(Fun, Acc, File)
  889:     end.
  890: 
  891: emulate_escript_foldl(Fun, Acc, File) ->
  892:     case escript:extract(File, [compile_source]) of
  893: 	{ok, [_Shebang, _Comment, _EmuArgs, Body]} ->
  894: 	    case Body of
  895: 		{source, BeamCode} ->
  896: 		    GetInfo = fun() -> file:read_file_info(File) end,
  897: 		    GetBin = fun() -> BeamCode end,
  898: 		    {ok, Fun(".", GetInfo, GetBin, Acc)};
  899: 		{beam, BeamCode} ->
  900: 		    GetInfo = fun() -> file:read_file_info(File) end,
  901: 		    GetBin = fun() -> BeamCode end,
  902: 		    {ok, Fun(".", GetInfo, GetBin, Acc)};
  903: 		{archive, ArchiveBin} ->
  904: 		    zip:foldl(Fun, Acc, {File, ArchiveBin})
  905: 	    end;
  906: 	{error, Reason} ->
  907: 	    {error, Reason}
  908:     end.
  909: 
  910: unicode(Config) when is_list(Config) ->
  911:     Data = ?config(data_dir, Config),
  912:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  913:     run(Dir, "unicode1",
  914:         [<<"escript: exception error: an error occurred when evaluating"
  915:            " an arithmetic expression\n  in operator  '/'/2\n     "
  916:            "called as <<224,170,170>> / <<224,170,170>>\nExitCode:127">>]),
  917:     run(Dir, "unicode2",
  918:         [<<"escript: exception error: an error occurred when evaluating"
  919:            " an arithmetic expression\n  in operator  '/'/2\n     "
  920:            "called as <<\"\xaa\">> / <<\"\xaa\">>\nExitCode:127">>]),
  921:     run(Dir, "unicode3", [<<"ExitCode:0">>]),
  922:     run(Dir, "unicode4", [<<"ExitCode:0">>]),
  923:     run(Dir, "unicode5", [<<"ExitCode:0">>]),
  924:     run(Dir, "unicode6", [<<"ExitCode:0">>]),
  925:     ok.
  926: 
  927: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  928: 
  929: overflow(Config) when is_list(Config) ->
  930:     Data = ?config(data_dir, Config),
  931:     Dir = filename:absname(Data),		%Get rid of trailing slash.
  932:     ?line run(Dir, "arg_overflow",
  933: 	      [<<"ExitCode:0">>]),
  934:     ?line run(Dir, "linebuf_overflow",
  935: 	      [<<"ExitCode:0">>]),
  936:     ok.
  937: 
  938: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  939: 
  940: run(Dir, Cmd0, Expected0) ->
  941:     Expected = iolist_to_binary(expected_output(Expected0, Dir)),
  942:     Cmd = case os:type() of
  943: 	      {win32,_} -> "escript " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0;
  944: 	      _ -> Cmd0
  945: 	  end,
  946:     do_run(Dir, Cmd, Expected).
  947: 
  948: run_with_opts(Dir, Opts, Cmd0, Expected) ->
  949:     Cmd = case os:type() of
  950: 	      {win32,_} -> "escript " ++ Opts ++ " " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0;
  951: 	      _ -> "escript " ++ Opts ++ " " ++ Dir ++ "/" ++ Cmd0
  952: 	  end,
  953:     do_run(Dir, Cmd, Expected).
  954: 
  955: do_run(Dir, Cmd, Expected0) ->
  956:     io:format("Run: ~p\n", [Cmd]),
  957:     Expected = iolist_to_binary(expected_output(Expected0, Dir)),
  958: 
  959:     Env = [{"PATH",Dir++":"++os:getenv("PATH")}],
  960:     Port = open_port({spawn,Cmd}, [exit_status,eof,in,{env,Env}]),
  961:     Res = get_data(Port, []),
  962:     receive
  963: 	{Port,{exit_status,ExitCode}} ->
  964: 	    case iolist_to_binary([Res,"ExitCode:"++integer_to_list(ExitCode)]) of
  965: 		Expected ->
  966: 		    ok;
  967: 		Actual ->
  968: 		    io:format("Expected: ~p\n", [Expected]),
  969: 		    io:format("Actual:   ~p\n", [Actual]),
  970: 		    ?t:fail()
  971: 	    end
  972:     end.
  973: 
  974: get_data(Port, SoFar) ->
  975:     receive
  976: 	{Port,{data,Bytes}} ->
  977: 	    get_data(Port, [SoFar|Bytes]);
  978: 	{Port,eof} ->
  979: 	    erlang:port_close(Port),
  980: 	    SoFar
  981:     end.
  982: 
  983: expected_output([data_dir|T], Data) ->
  984:     Slash = case os:type() of
  985: 		{win32,_} -> "\\";
  986: 		_ -> "/"
  987: 	    end,
  988:     [filename:nativename(Data)++Slash|expected_output(T, Data)];
  989: expected_output([H|T], Data) ->
  990:     [H|expected_output(T, Data)];
  991: expected_output([], _) ->
  992:     [];
  993: expected_output(Bin, _) when is_binary(Bin) ->
  994:     Bin.
  995: