%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2007-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(escript_SUITE). -export([ all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2, basic/1, errors/1, strange_name/1, emulator_flags/1, emulator_flags_no_shebang/1, two_lines/1, module_script/1, beam_script/1, archive_script/1, archive_script_file_access/1, epp/1, create_and_extract/1, foldl/1, overflow/1, verify_sections/3, unicode/1 ]). -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,5}}]. all() -> [basic, errors, strange_name, emulator_flags, emulator_flags_no_shebang, two_lines, module_script, beam_script, archive_script, epp, create_and_extract, foldl, overflow, archive_script_file_access, unicode]. 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(_Case, Config) -> Config. end_per_testcase(_Case, _Config) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% basic(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "factorial 5", <<"factorial 5 = 120\nExitCode:0">>), run(Dir, "factorial_compile 10", <<"factorial 10 = 3628800\nExitCode:0">>), run(Dir, "factorial_compile_main 7", <<"factorial 7 = 5040\nExitCode:0">>), run(Dir, "factorial_warning 20", [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\n" "factorial 20 = 2432902008176640000\nExitCode:0">>]), run_with_opts(Dir, "-s", "factorial_warning", [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]), run_with_opts(Dir, "-s -i", "factorial_warning", [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]), run_with_opts(Dir, "-c -s", "factorial_warning", [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]), run(Dir, "filesize "++filename:join(proplists:get_value(data_dir, Config),"filesize"), [data_dir,<<"filesize:11: Warning: function id/1 is unused\n324\nExitCode:0">>]), run(Dir, "test_script_name", [data_dir,<<"test_script_name\nExitCode:0">>]), run(Dir, "tail_rec 1000", [<<"ok\nExitCode:0">>]), %% We expect the trap_exit flag for the process to be false, %% since that is the default state for newly spawned processes. run(Dir, "trap_exit", <<"false\nExitCode:0">>), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% errors(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "compile_error", [data_dir,<<"compile_error:5: syntax error before: '*'\n">>, data_dir,<<"compile_error:8: syntax error before: blarf\n">>, <<"escript: There were compilation errors.\nExitCode:127">>]), run(Dir, "lint_error", [data_dir,<<"lint_error:6: function main/1 already defined\n">>, data_dir,"lint_error:8: variable 'ExitCode' is unbound\n", <<"escript: There were compilation errors.\nExitCode:127">>]), run_with_opts(Dir, "-s", "lint_error", [data_dir,<<"lint_error:6: function main/1 already defined\n">>, data_dir,"lint_error:8: variable 'ExitCode' is unbound\n", <<"escript: There were compilation errors.\nExitCode:127">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% strange_name(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "strange.name -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% emulator_flags(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "emulator_flags -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[{nostick,[]}]\n" "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% two_lines(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "two_lines -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% emulator_flags_no_shebang(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. %% Need run_with_opts, to always use "escript" explicitly run_with_opts(Dir, "", "emulator_flags_no_shebang -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[{nostick,[]}]\n" "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Pick the source code from the emulator_flags script %% Generate a new escript with a module header module_script(Config) when is_list(Config) -> %% Read orig file Data = proplists:get_value(data_dir, Config), OrigFile = filename:join([Data,"emulator_flags"]), {ok, OrigBin} = file:read_file(OrigFile), [Shebang, Mode, Flags | Source] = string:tokens(binary_to_list(OrigBin), "\n"), {ok, OrigFI} = file:read_file_info(OrigFile), %% Write source file Priv = proplists:get_value(priv_dir, Config), Dir = filename:absname(Priv), % Get rid of trailing slash. Base = "module_script", ErlFile = filename:join([Priv, Base ++ ".erl"]), ErlCode = ["\n-module(", Base, ").\n", "-export([main/1]).\n\n", string:join(Source, "\n"), "\n"], ok = file:write_file(ErlFile, ErlCode), %%%%%%% %% Create and run scripts without emulator flags %% With shebang NoArgsBase = Base ++ "_no_args_with_shebang", NoArgsFile = filename:join([Priv, NoArgsBase]), ok = file:write_file(NoArgsFile, [Shebang, "\n", ErlCode]), ok = file:write_file_info(NoArgsFile, OrigFI), run(Dir, NoArgsBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), run_with_opts(Dir, "", NoArgsBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), %% Without shebang NoArgsBase2 = Base ++ "_no_args_without_shebang", NoArgsFile2 = filename:join([Priv, NoArgsBase2]), ok = file:write_file(NoArgsFile2, ["Something else than shebang!!!", "\n", ErlCode]), ok = file:write_file_info(NoArgsFile2, OrigFI), run_with_opts(Dir, "", NoArgsBase2 ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), %% Plain module without header NoArgsBase3 = Base ++ "_no_args_without_header", NoArgsFile3 = filename:join([Priv, NoArgsBase3]), ok = file:write_file(NoArgsFile3, [ErlCode]), ok = file:write_file_info(NoArgsFile3, OrigFI), run_with_opts(Dir, "", NoArgsBase3 ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), %%%%%%% %% Create and run scripts with emulator flags %% With shebang ArgsBase = Base ++ "_args_with_shebang", ArgsFile = filename:join([Priv, ArgsBase]), ok = file:write_file(ArgsFile, [Shebang, "\n", Mode, "\n", Flags, "\n", ErlCode]), ok = file:write_file_info(ArgsFile, OrigFI), run(Dir, ArgsBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[{nostick,[]}]\n" "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Pick the source code from the emulator_flags script and compile it. %% Generate a new escript containing the beam code and the escript header beam_script(Config) when is_list(Config) -> %% Read orig file Data = proplists:get_value(data_dir, Config), OrigFile = filename:join([Data,"emulator_flags"]), {ok, OrigBin} = file:read_file(OrigFile), [Shebang, Mode, Flags | Source] = string:tokens(binary_to_list(OrigBin), "\n"), {ok, OrigFI} = file:read_file_info(OrigFile), %% Write source file Priv = proplists:get_value(priv_dir, Config), Dir = filename:absname(Priv), % Get rid of trailing slash. Base = "beam_script", ErlFile = filename:join([Priv, Base ++ ".erl"]), ok = file:write_file(ErlFile, ["\n-module(", Base, ").\n", "-export([main/1]).\n\n", string:join(Source, "\n"), "\n"]), %% Compile the code {ok, _Mod, BeamCode} = compile:file(ErlFile, [binary]), %%%%%%% %% Create and run scripts without emulator flags %% With shebang NoArgsBase = Base ++ "_no_args_with_shebang", NoArgsFile = filename:join([Priv, NoArgsBase]), ok = file:write_file(NoArgsFile, [Shebang, "\n", BeamCode]), ok = file:write_file_info(NoArgsFile, OrigFI), run(Dir, NoArgsBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), run_with_opts(Dir, "", NoArgsBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), %% Without shebang NoArgsBase2 = Base ++ "_no_args_without_shebang", NoArgsFile2 = filename:join([Priv, NoArgsBase2]), ok = file:write_file(NoArgsFile2, ["Something else than shebang!!!", "\n", BeamCode]), ok = file:write_file_info(NoArgsFile2, OrigFI), run_with_opts(Dir, "", NoArgsBase2 ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), %% Plain beam file without header NoArgsBase3 = Base ++ "_no_args_without_header", NoArgsFile3 = filename:join([Priv, NoArgsBase3]), ok = file:write_file(NoArgsFile3, [BeamCode]), ok = file:write_file_info(NoArgsFile3, OrigFI), run_with_opts(Dir, "", NoArgsBase3 ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[]\n" "mnesia:[]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), %%%%%%% %% Create and run scripts with emulator flags %% With shebang ArgsBase = Base ++ "_args", ArgsFile = filename:join([Priv, ArgsBase]), ok = file:write_file(ArgsFile, [Shebang, "\n", Mode, "\n", Flags, "\n", BeamCode]), ok = file:write_file_info(ArgsFile, OrigFI), run(Dir, ArgsBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "nostick:[{nostick,[]}]\n" "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Create an archive file containing two entire applications plus two %% alternate main modules. Generate a new escript containing the archive %% (with .app and .beam files and) and the escript header. archive_script(Config) when is_list(Config) -> %% Copy the orig files to priv_dir DataDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), Archive = filename:join([PrivDir, "archive_script.zip"]), {ok, _} = zip:create(Archive, ["archive_script"], [{compress, []}, {cwd, DataDir}]), {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]), TopDir = filename:join([PrivDir, "archive_script"]), %% Compile the code ok = compile_app(TopDir, "archive_script_dict"), ok = compile_app(TopDir, "archive_script_dummy"), {ok, MainFiles} = file:list_dir(TopDir), ok = compile_files(MainFiles, TopDir, TopDir), %% Create the archive {ok, TopFiles} = file:list_dir(TopDir), {ok, {_, ArchiveBin}} = zip:create(Archive, TopFiles, [memory, {compress, []}, {cwd, TopDir}]), %% Read the source script OrigFile = filename:join([DataDir, "emulator_flags"]), {ok, OrigBin} = file:read_file(OrigFile), [Shebang, Mode, _Flags | _Source] = string:tokens(binary_to_list(OrigBin), "\n"), Flags = "%%! -archive_script_dict foo bar" " -archive_script_dict foo" " -archive_script_dummy bar", {ok, OrigFI} = file:read_file_info(OrigFile), %%%%%%% %% Create and run scripts without emulator flags MainBase = "archive_script_main", MainScript = filename:join([PrivDir, MainBase]), %% With shebang ok = file:write_file(MainScript, [Shebang, "\n", Flags, "\n", ArchiveBin]), ok = file:write_file_info(MainScript, OrigFI), run(PrivDir, MainBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n" "dummy:[{archive_script_dummy,[\"bar\"]}]\n" "priv:{ok,<<\"Some private data...\\n\">>}\n" "ExitCode:0">>]), run_with_opts(PrivDir, "", MainBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n" "dummy:[{archive_script_dummy,[\"bar\"]}]\n" "priv:{ok,<<\"Some private data...\\n\">>}\n" "ExitCode:0">>]), ok = file:rename(MainScript, MainScript ++ "_with_shebang"), %% Without shebang (no flags) ok = file:write_file(MainScript, ["Something else than shebang!!!", "\n", ArchiveBin]), ok = file:write_file_info(MainScript, OrigFI), run_with_opts(PrivDir, "", MainBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "dict:[]\n" "dummy:[]\n" "priv:{ok,<<\"Some private data...\\n\">>}\n" "ExitCode:0">>]), ok = file:rename(MainScript, MainScript ++ "_without_shebang"), %% Plain archive without header (no flags) ok = file:write_file(MainScript, [ArchiveBin]), ok = file:write_file_info(MainScript, OrigFI), run_with_opts(PrivDir, "", MainBase ++ " -arg1 arg2 arg3", [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n" "dict:[]\n" "dummy:[]\n" "priv:{ok,<<\"Some private data...\\n\">>}\n" "ExitCode:0">>]), ok = file:rename(MainScript, MainScript ++ "_without_header"), %%%%%%% %% Create and run scripts with emulator flags AltBase = "archive_script_alternate_main", AltScript = filename:join([PrivDir, AltBase]), ok = file:write_file(AltScript, [Shebang, "\n", Mode, "\n", Flags, " -escript main archive_script_main2\n", ArchiveBin]), ok = file:write_file_info(AltScript, OrigFI), run(PrivDir, AltBase ++ " -arg1 arg2 arg3", [<<"main2:[\"-arg1\",\"arg2\",\"arg3\"]\n" "dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n" "dummy:[{archive_script_dummy,[\"bar\"]}]\n" "priv:{ok,<<\"Some private data...\\n\">>}\n" "ExitCode:0">>]), ok. %% Test the correction of OTP-10071 %% The errors identified are %% %% a) If primary archive was named "xxx", then a file in the same %% directory named "xxxyyy" would be interpreted as a file named yyy %% inside the archive. %% %% b) erl_prim_loader did not correctly create and normalize absolute %% paths for primary archive and files inside it, so unless given %% with exact same path files inside the archive would not be %% found. E.g. if escript was started as ./xxx then "xxx/file" %% would not be found since erl_prim_loader would try to match %% /full/path/to/xxx with /full/path/to/./xxx. Same problem with %% ../. Also, the use of symlinks in the path to the archive would %% cause problems. %% %% c) Depending on how the primary archive was built, %% erl_prim_loader:list_dir/1 would sometimes return an empty string %% inside the file list. This was a virtual element representing the %% top directory of the archive. This shall not occur. %% archive_script_file_access(Config) when is_list(Config) -> %% Copy the orig files to priv_dir DataDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), MainMod = "archive_script_file_access", MainSrc = MainMod ++ ".erl", MainBeam = MainMod ++ ".beam", Archive = filename:join([PrivDir, "archive_script_file_access.zip"]), {ok, _} = zip:create(Archive, ["archive_script_file_access"], [{compress, []}, {cwd, DataDir}]), {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]), TopDir = filename:join([PrivDir, "archive_script_file_access"]), %% Compile the code ok = compile_files([MainSrc], TopDir, TopDir), %% First, create a file structure which will be included in the archive: %% %% dir1/ %% dir1/subdir1/ %% dir1/subdir1/file1 %% {ok, OldDir} = file:get_cwd(), ok = file:set_cwd(TopDir), DummyDir = "dir1", DummySubDir = filename:join(DummyDir, "subdir1"), RelDummyFile = filename:join(DummySubDir, "file1"), DummyFile = filename:join(TopDir,RelDummyFile), ok = filelib:ensure_dir(DummyFile), ok = file:write_file(DummyFile, ["foo\nbar\nbaz"]), %% 1. Create zip archive by adding the dummy file and the beam %% file as binaries to zip. %% %% This used to provoke the following issues when the script was run as %% "./": %% a. erl_prim_loader:read_file_info/1 returning 'error' %% b. erl_prim_loader:list_dir/1 returning {ok, ["dir1", [], "file1"]} %% leading to an infinite loop in reltool_target:spec_dir/1 Files1 = lists:map(fun(Filename) -> {ok, Bin} = file:read_file(Filename), {Filename,Bin} end, [RelDummyFile,MainBeam]), {ok, {"mem", Bin1}} = zip:create("mem", Files1, [memory]), %% Create the escript ScriptName1 = "archive_script_file_access1", Script1 = filename:join([PrivDir, ScriptName1]), Flags = "-escript main " ++ MainMod, ok = escript:create(Script1,[shebang,{emu_args,Flags},{archive,Bin1}]), ok = file:change_mode(Script1,8#00744), %% If supported, create a symlink to the script. This is used to %% test error b) described above this test case. SymlinkName1 = "symlink_to_"++ScriptName1, Symlink1 = filename:join([PrivDir, SymlinkName1]), file:make_symlink(ScriptName1,Symlink1), % will fail if not supported %% Also add a dummy file in the same directory with the same name %% as the script except is also has an extension. This used to %% test error a) described above this test case. ok = file:write_file(Script1 ++ ".extension", <<"same name as script, but with extension">>), %% Change to script's directory and run it as "./" ok = file:set_cwd(PrivDir), run(PrivDir, "./" ++ ScriptName1 ++ " " ++ ScriptName1, [<<"ExitCode:0">>]), ok = file:set_cwd(TopDir), %% 2. Create zip archive by letting zip read the files from the file system %% %% The difference compared to the archive_script_file_access1 is %% that this will have a file element for each directory in the %% archive - while archive_script_file_access1 will only have a %% file element per regular file. Files2 = [DummyDir,MainBeam], {ok, {"mem", Bin2}} = zip:create("mem", Files2, [memory]), %% Create the escript ScriptName2 = "archive_script_file_access2", Script2 = filename:join([PrivDir, ScriptName2]), ok = escript:create(Script2,[shebang,{emu_args,Flags},{archive,Bin2}]), ok = file:change_mode(Script2,8#00744), %% Also add a dummy file in the same directory with the same name %% as the script except is also has an extension. This used to %% test error a) described above this test case. ok = file:write_file(Script2 ++ ".extension", <<"same name as script, but with extension">>), %% If supported, create a symlink to the script. This is used to %% test error b) described above this test case. SymlinkName2 = "symlink_to_"++ScriptName2, Symlink2 = filename:join([PrivDir, SymlinkName2]), file:make_symlink(ScriptName2,Symlink2), % will fail if not supported %% Change to script's directory and run it as "./" ok = file:set_cwd(PrivDir), run(PrivDir, "./" ++ ScriptName2 ++ " " ++ ScriptName2, [<<"ExitCode:0">>]), %% 3. If symlinks are supported, run one of the scripts via a symlink. %% %% This is in order to test error b) described above this test case. case element(1,os:type()) =:= win32 orelse file:read_link(Symlink2) of {ok,_} -> run(PrivDir, "./" ++ SymlinkName2 ++ " " ++ ScriptName2, [<<"ExitCode:0">>]); _ -> % not supported ok end, ok = file:set_cwd(OldDir). 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},report_errors]) of {ok, _Mod} -> compile_files(Files, SrcDir, OutDir); Error -> {compilation_error, AbsFile, OutDir, Error} end; _ -> compile_files(Files, SrcDir, OutDir) end; compile_files([], _, _) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% epp(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "factorial_epp 5", <<"factorial 5 = 120\nExitCode:0">>), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% create_and_extract(Config) when is_list(Config) -> {NewFile, FileInfo, EmuArg, Source, _ErlBase, ErlCode, _BeamBase, BeamCode, ArchiveBin} = prepare_creation("create_and_extract", Config), Bodies = [[{source, ErlCode}], [{beam, BeamCode}], [{archive, ArchiveBin}]], %% Verify all combinations of scripts with shebangs [verify_sections(NewFile, FileInfo, S ++ C ++ E ++ B) || S <- [[{shebang, default}], [{shebang, "/usr/bin/env escript"}]], C <- [[], [{comment, undefined}], [{comment, default}], [{comment, "This is a nonsense comment"}]], E <- [[], [{emu_args, undefined}], [{emu_args, EmuArg}]], B <- [[{source, Source}] | Bodies]], %% Verify all combinations of scripts without shebangs [verify_sections(NewFile, FileInfo, S ++ C ++ E ++ B) || S <- [[], [{shebang, undefined}]], C <- [[], [{comment, undefined}]], E <- [[], [{emu_args, undefined}]], B <- Bodies], %% Verify the compile_source option file:delete(NewFile), ok = escript:create(NewFile, [{source, Source}]), {ok, [_, _, _, {source, Source}]} = escript:extract(NewFile, []), {ok, [_, _, _, {source, BeamCode2}]} = escript:extract(NewFile, [compile_source]), verify_sections(NewFile, FileInfo, [{shebang, default}, {comment, default}, {beam, BeamCode2}]), file:delete(NewFile), ok. prepare_creation(Base, Config) -> %% Read the source PrivDir = proplists:get_value(priv_dir, Config), DataDir = proplists:get_value(data_dir, Config), OrigFile = filename:join([DataDir,"emulator_flags"]), {ok, FileInfo} = file:read_file_info(OrigFile), NewFile = filename:join([PrivDir, Base]), {ok, [{shebang, default}, {comment, _}, {emu_args, EmuArg}, {source, Source}]} = escript:extract(OrigFile, []), %% Compile the code ErlFile = NewFile ++ ".erl", ErlCode = list_to_binary(["\n-module(", Base, ").\n", "-export([main/1]).\n\n", Source, "\n\n"]), ok = file:write_file(ErlFile, ErlCode), %% Compile the code {ok, _Mod, BeamCode} = compile:file(ErlFile, [binary, debug_info]), %% Create an archive {ok, {_, ArchiveBin}} = zip:create("dummy_archive_name", [{Base ++ ".erl", ErlCode}, {Base ++ ".beam", BeamCode}], [{compress, []}, memory]), {NewFile, FileInfo, EmuArg, Source, Base ++ ".erl", ErlCode, Base ++ ".beam", BeamCode, ArchiveBin}. verify_sections(File, FileInfo, Sections) -> io:format("~p:verify_sections(\n\t~p,\n\t~p,\n\t~p).\n", [?MODULE, File, FileInfo, Sections]), %% Create file:delete(File), ok = escript:create(File, Sections), ok = file:write_file_info(File, FileInfo), %% Run Dir = filename:absname(filename:dirname(File)), Base = filename:basename(File), HasArg = fun(Tag) -> case lists:keysearch(Tag, 1, Sections) of false -> false; {value, {_, undefined}} -> false; {value, _} -> true end end, ExpectedMain = <<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n">>, ExpectedOutput = case HasArg(emu_args) of true -> <<"nostick:[{nostick,[]}]\n" "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n" "ERL_FLAGS=false\n" "unknown:[]\n" "ExitCode:0">>; false -> <<"nostick:[]\nmnesia:[]\nERL_FLAGS=false\nunknown:[]\nExitCode:0">> end, InputArgs = Base ++ " -arg1 arg2 arg3", Expected = <>, case HasArg(shebang) of true -> run(Dir, InputArgs, [Expected]); false -> run_with_opts(Dir, [], InputArgs, [Expected]) end, %% Verify {ok, Bin} = escript:create(binary, Sections), {ok, Read} = file:read_file(File), Bin = Read, % Assert Normalized = normalize_sections(Sections), {ok, Extracted} = escript:extract(File, []), io:format("Normalized; ~p\n", [Normalized]), io:format("Extracted ; ~p\n", [Extracted]), Normalized = Extracted, % Assert ok. normalize_sections(Sections) -> AtomToTuple = fun(Val) -> if is_atom(Val) -> {Val, default}; true -> Val end end, case lists:map(AtomToTuple, [{K, V} || {K, V} <- Sections, V =/= undefined]) of [{shebang, Shebang} | Rest] -> [{shebang, Shebang} | case Rest of [{comment, Comment} | Rest2] -> [{comment, Comment} | case Rest2 of [{emu_args, EmuArgs}, Body] -> [{emu_args, EmuArgs}, Body]; [Body] -> [{emu_args, undefined}, Body] end ]; [{emu_args, EmuArgs}, Body] -> [{comment, undefined}, {emu_args, EmuArgs}, Body]; [Body] -> [{comment, undefined}, {emu_args, undefined}, Body] end ]; [Body] -> [{shebang, undefined}, {comment, undefined}, {emu_args, undefined}, Body] end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% foldl(Config) when is_list(Config) -> {NewFile, _FileInfo, _EmuArg, _Source, ErlBase, ErlCode, BeamBase, _BeamCode, ArchiveBin} = prepare_creation("foldl", Config), Collect = fun(Name, GetInfo, GetBin, Acc) -> [{Name, GetInfo(), GetBin()} | Acc] end, %% Get line numbers and the file attribute right SourceFile = NewFile ++ ".erl", <<_:1/binary, ErlCode2/binary>> = ErlCode, ok = file:write_file(SourceFile, ErlCode2), {ok, _Mod, BeamCode} = compile:file(SourceFile, [binary, debug_info]), %% Verify source script ok = escript:create(SourceFile, [{source, ErlCode}]), {ok, [{".", _, BeamCode2}]} = escript_foldl(Collect, [], SourceFile), {ok, Abstr} = beam_lib:chunks(BeamCode, [abstract_code]), {ok, Abstr2} = beam_lib:chunks(BeamCode2, [abstract_code]), %% io:format("abstr1=~p\n", [Abstr]), %% io:format("abstr2=~p\n", [Abstr2]), Abstr = Abstr2, % Assert %% Verify beam script ok = escript:create(NewFile, [{beam, BeamCode}]), {ok, [{".", _, BeamCode}]} = escript_foldl(Collect, [], NewFile), %% Verify archive scripts ok = escript:create(NewFile, [{archive, ArchiveBin}]), {ok, [{BeamBase, #file_info{}, _}, {ErlBase, #file_info{}, _}]} = escript_foldl(Collect, [], NewFile), ArchiveFiles = [{ErlBase, ErlCode}, {BeamBase, BeamCode}], ok = escript:create(NewFile, [{archive, ArchiveFiles, []}]), {ok, [{BeamBase, _, _}, {ErlBase, _, _}]} = escript_foldl(Collect, [], NewFile), ok. escript_foldl(Fun, Acc, File) -> code:ensure_loaded(zip), case erlang:function_exported(zip, foldl, 3) of true -> emulate_escript_foldl(Fun, Acc, File); false -> escript:foldl(Fun, Acc, File) end. emulate_escript_foldl(Fun, Acc, File) -> case escript:extract(File, [compile_source]) of {ok, [_Shebang, _Comment, _EmuArgs, Body]} -> case Body of {source, BeamCode} -> GetInfo = fun() -> file:read_file_info(File) end, GetBin = fun() -> BeamCode end, {ok, Fun(".", GetInfo, GetBin, Acc)}; {beam, BeamCode} -> GetInfo = fun() -> file:read_file_info(File) end, GetBin = fun() -> BeamCode end, {ok, Fun(".", GetInfo, GetBin, Acc)}; {archive, ArchiveBin} -> zip:foldl(Fun, Acc, {File, ArchiveBin}) end; {error, Reason} -> {error, Reason} end. unicode(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "unicode1", [<<"escript: exception error: an error occurred when evaluating" " an arithmetic expression\n in operator '/'/2\n " "called as <<224,170,170>> / <<224,170,170>>\nExitCode:127">>]), run(Dir, "unicode2", [<<"escript: exception error: an error occurred when evaluating" " an arithmetic expression\n in operator '/'/2\n " "called as <<\"\xaa\">> / <<\"\xaa\">>\nExitCode:127">>]), run(Dir, "unicode3", [<<"ExitCode:0">>]), run(Dir, "unicode4", [<<"ExitCode:0">>]), run(Dir, "unicode5", [<<"ExitCode:0">>]), run(Dir, "unicode6", [<<"ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% overflow(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Dir = filename:absname(Data), %Get rid of trailing slash. run(Dir, "arg_overflow", [<<"ExitCode:0">>]), run(Dir, "linebuf_overflow", [<<"ExitCode:0">>]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% run(Dir, Cmd0, Expected0) -> Expected = iolist_to_binary(expected_output(Expected0, Dir)), Cmd = case os:type() of {win32,_} -> "escript " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0; _ -> Cmd0 end, do_run(Dir, Cmd, Expected). run_with_opts(Dir, Opts, Cmd0, Expected) -> Cmd = case os:type() of {win32,_} -> "escript " ++ Opts ++ " " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0; _ -> "escript " ++ Opts ++ " " ++ Dir ++ "/" ++ Cmd0 end, do_run(Dir, Cmd, Expected). do_run(Dir, Cmd, Expected0) -> io:format("Run: ~p\n", [Cmd]), Expected = iolist_to_binary(expected_output(Expected0, Dir)), Env = [{"PATH",Dir++":"++os:getenv("PATH")}], Port = open_port({spawn,Cmd}, [exit_status,eof,in,{env,Env}]), Res = get_data(Port, []), receive {Port,{exit_status,ExitCode}} -> case iolist_to_binary([Res,"ExitCode:"++integer_to_list(ExitCode)]) of Expected -> ok; Actual -> io:format("Expected: ~p\n", [Expected]), io:format("Actual: ~p\n", [Actual]), ct:fail(failed) end end. get_data(Port, SoFar) -> receive {Port,{data,Bytes}} -> get_data(Port, [SoFar|Bytes]); {Port,eof} -> erlang:port_close(Port), SoFar end. expected_output([data_dir|T], Data) -> Slash = case os:type() of {win32,_} -> "\\"; _ -> "/" end, [filename:nativename(Data)++Slash|expected_output(T, Data)]; expected_output([H|T], Data) -> [H|expected_output(T, Data)]; expected_output([], _) -> []; expected_output(Bin, _) when is_binary(Bin) -> Bin.