%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2018. 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(erlc_SUITE). %% Tests the erlc command by compiling various types of files. -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, compile_erl/1, compile_yecc/1, compile_script/1, compile_mib/1, good_citizen/1, deep_cwd/1, arg_overflow/1, make_dep_options/1]). -include_lib("common_test/include/ct.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [compile_erl, compile_yecc, compile_script, compile_mib, good_citizen, deep_cwd, arg_overflow, make_dep_options]. groups() -> []. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %% Copy from erlc_SUITE_data/include/erl_test.hrl. -record(person, {name, shoe_size}). %% Tests that compiling Erlang source code works. compile_erl(Config) when is_list(Config) -> {SrcDir, OutDir, Cmd} = get_cmd(Config), FileName = filename:join(SrcDir, "erl_test_ok.erl"), %% By default, warnings are now turned on. run(Config, Cmd, FileName, "", ["Warning: function foo/0 is unused\$", "_OK_"]), %% Test that the compiled file is where it should be, %% and that it is runnable. {module, erl_test_ok} = code:load_abs(filename:join(OutDir, "erl_test_ok")), 42 = erl_test_ok:shoe_size(#person{shoe_size=42}), code:purge(erl_test_ok), %% Try disabling warnings. run(Config, Cmd, FileName, "-W0", ["_OK_"]), %% Try treating warnings as errors. run(Config, Cmd, FileName, "-Werror", ["compile: warnings being treated as errors\$", "function foo/0 is unused\$", "_ERROR_"]), %% Check a bad file. BadFile = filename:join(SrcDir, "erl_test_bad.erl"), run(Config, Cmd, BadFile, "", ["function non_existing/1 undefined\$", "_ERROR_"]), ok. %% Test that compiling yecc source code works. compile_yecc(Config) when is_list(Config) -> {SrcDir, _, OutDir} = get_dirs(Config), Cmd = erlc() ++ " -o" ++ OutDir ++ " ", FileName = filename:join(SrcDir, "yecc_test_ok.yrl"), run(Config, Cmd, FileName, "-W0", ["_OK_"]), true = exists(filename:join(OutDir, "yecc_test_ok.erl")), BadFile = filename:join(SrcDir, "yecc_test_bad.yrl"), run(Config, Cmd, BadFile, "-W0", ["rootsymbol form is not a nonterminal\$", "undefined nonterminal: form\$", "Nonterminals is missing\$", "_ERROR_"]), exists(filename:join(OutDir, "yecc_test_ok.erl")), ok. %% Test that compiling start scripts works. compile_script(Config) when is_list(Config) -> {SrcDir, OutDir, Cmd} = get_cmd(Config), FileName = filename:join(SrcDir, "start_ok.script"), run(Config, Cmd, FileName, "", ["_OK_"]), true = exists(filename:join(OutDir, "start_ok.boot")), BadFile = filename:join(SrcDir, "start_bad.script"), run(Config, Cmd, BadFile, "", ["syntax error before:", "_ERROR_"]), ok. %% Test that compiling SNMP mibs works. compile_mib(Config) when is_list(Config) -> {SrcDir, OutDir, Cmd} = get_cmd(Config), FileName = filename:join(SrcDir, "GOOD-MIB.mib"), run(Config, Cmd, FileName, "", ["_OK_"]), Output = filename:join(OutDir, "GOOD-MIB.bin"), true = exists(Output), %% Try -W option. ok = file:delete(Output), run(Config, Cmd, FileName, "-W", ["_OK_"]), true = exists(Output), %% Try -W option and more verbose. ok = file:delete(Output), case test_server:os_type() of {unix,_} -> run(Config, Cmd, FileName, "-W +'{verbosity,info}'", ["\\[GOOD-MIB[.]mib\\]\\[INF\\]: No accessfunction for 'sysDescr' => using default", "_OK_"]), true = exists(Output), ok = file:delete(Output); _ -> ok %Don't bother -- too much work. end, %% Try a bad file. BadFile = filename:join(SrcDir, "BAD-MIB.mib"), run(Config, Cmd, BadFile, "", ["BAD-MIB.mib: 1: syntax error before: mibs\$", "compilation_failed_ERROR_"]), %% Make sure that no -I option works. NewCmd = erlc() ++ " -o" ++ OutDir ++ " ", run(Config, NewCmd, FileName, "", ["_OK_"]), true = exists(Output), ok. %% Checks that 'erlc' doesn't eat any input (important when called from a %% shell script with redirected input). good_citizen(Config) when is_list(Config) -> case os:type() of {unix, _} -> PrivDir = proplists:get_value(priv_dir, Config), Answer = filename:join(PrivDir, "answer"), Script = filename:join(PrivDir, "test_script"), Test = filename:join(PrivDir, "test.erl"), S = ["#! /bin/sh\n", "erlc ", Test, "\n", "read reply\n", "echo $reply\n"], ok = file:write_file(Script, S), ok = file:write_file(Test, "-module(test).\n"), Cmd = "echo y | sh " ++ Script ++ " > " ++ Answer, os:cmd(Cmd), {ok, Answer0} = file:read_file(Answer), [$y|_] = binary_to_list(Answer0), ok; _ -> {skip, "Unix specific"} end. %% Make sure that compiling an Erlang module deep down in %% in a directory with more than 255 characters works. deep_cwd(Config) when is_list(Config) -> case os:type() of {unix, _} -> PrivDir = proplists:get_value(priv_dir, Config), deep_cwd_1(PrivDir); _ -> {skip, "Only a problem on Unix"} end. deep_cwd_1(PrivDir) -> DeepDir0 = filename:join(PrivDir, lists:duplicate(128, $a)), DeepDir = filename:join(DeepDir0, lists:duplicate(128, $b)), ok = file:make_dir(DeepDir0), ok = file:make_dir(DeepDir), ok = file:set_cwd(DeepDir), ok = file:write_file("test.erl", "-module(test).\n\n"), io:format("~s\n", [os:cmd("erlc test.erl")]), true = filelib:is_file("test.beam"), ok. %% Test that a large number of command line switches does not %% overflow the argument buffer arg_overflow(Config) when is_list(Config) -> {SrcDir, _OutDir, Cmd} = get_cmd(Config), FileName = filename:join(SrcDir, "erl_test_ok.erl"), %% Each -D option will be expanded to three arguments when %% invoking 'erl'. NumDOptions = num_d_options(), Args = lists:flatten([ ["-D", integer_to_list(N, 36), "=1 "] || N <- lists:seq(1, NumDOptions) ]), run(Config, Cmd, FileName, Args, ["Warning: function foo/0 is unused\$", "_OK_"]), ok. num_d_options() -> case {os:type(),os:version()} of {{win32,_},_} -> %% The maximum size of a command line in the command %% shell on Windows is 8191 characters. %% Each -D option is expanded to "@dv NN 1", i.e. %% 8 characters. (Numbers up to 1295 can be expressed %% as two 36-base digits.) 1000; {{unix,linux},Version} when Version < {2,6,23} -> %% On some older 64-bit versions of Linux, the maximum number %% of arguments is 16383. %% See: http://www.in-ulm.de/~mascheck/various/argmax/ 5440; {{unix,darwin},{Major,_,_}} when Major >= 11 -> %% "getconf ARG_MAX" still reports 262144 (as in previous %% version of MacOS X), but the useful space seem to have %% shrunk significantly (or possibly the number of arguments). %% 7673 7500; {_,_} -> 12000 end. erlc() -> case os:find_executable("erlc") of false -> ct:fail("Can't find erlc"); Erlc -> "\"" ++ Erlc ++ "\"" end. make_dep_options(Config) -> {SrcDir,OutDir,Cmd} = get_cmd(Config), FileName = filename:join(SrcDir, "erl_test_ok.erl"), BeamFileName = filename:join(OutDir, "erl_test_ok.beam"), DepRE = ["/erl_test_ok[.]beam: \\\\$", "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$", "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$", "_OK_"], DepRETarget = ["^target: \\\\$", "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$", "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$", "_OK_"], DepREMP = ["/erl_test_ok[.]beam: \\\\$", "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$", "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$", [], "/system_test/erlc_SUITE_data/include/erl_test.hrl:$", "_OK_"], DepREMissing = ["/erl_test_missing_header[.]beam: \\\\$", "/system_test/erlc_SUITE_data/src/erl_test_missing_header[.]erl \\\\$", "/system_test/erlc_SUITE_data/include/erl_test[.]hrl \\\\$", "missing.hrl$", "_OK_"], file:delete(BeamFileName), %% Test plain -M run(Config, Cmd, FileName, "-M", DepRE), false = exists(BeamFileName), %% Test -MF File DepFile = filename:join(OutDir, "my.deps"), run(Config, Cmd, FileName, "-MF "++DepFile, ["_OK_"]), {ok,MFBin} = file:read_file(DepFile), verify_result(binary_to_list(MFBin)++["_OK_"], DepRE), false = exists(BeamFileName), %% Test -MD run(Config, Cmd, FileName, "-MD", ["_OK_"]), MDFile = filename:join(OutDir, "erl_test_ok.Pbeam"), {ok,MFBin} = file:read_file(MDFile), file:delete(MDFile), %% used further down! false = exists(BeamFileName), %% Test -M -MT Target run(Config, Cmd, FileName, "-M -MT target", DepRETarget), false = exists(BeamFileName), %% Test -MF File -MT Target TargetDepFile = filename:join(OutDir, "target.deps"), run(Config, Cmd, FileName, "-MF "++TargetDepFile++" -MT target", ["_OK_"]), {ok,TargetBin} = file:read_file(TargetDepFile), verify_result(binary_to_list(TargetBin)++["_OK_"], DepRETarget), file:delete(TargetDepFile), false = exists(BeamFileName), %% Test -MD -MT Target run(Config, Cmd, FileName, "-MD -MT target", ["_OK_"]), TargetMDFile = filename:join(OutDir, "erl_test_ok.Pbeam"), {ok,TargetBin} = file:read_file(TargetMDFile), file:delete(TargetDepFile), false = exists(BeamFileName), %% Test -M -MQ Target. (Note: Passing a $ on the command line %% portably for Unix and Windows is tricky, so we will just test %% that MQ works at all.) run(Config, Cmd, FileName, "-M -MQ target", DepRETarget), false = exists(BeamFileName), %% Test -M -MP run(Config, Cmd, FileName, "-M -MP", DepREMP), false = exists(BeamFileName), %% Test -M -MG MissingHeader = filename:join(SrcDir, "erl_test_missing_header.erl"), run(Config, Cmd, MissingHeader, "-M -MG", DepREMissing), false = exists(BeamFileName), %% %% check the above variants with side-effect -MMD %% %% since compiler is run on the erlang code a warning will be %% issued by the compiler, match that. WarningRE = "/system_test/erlc_SUITE_data/src/erl_test_ok.erl:[0-9]+: " "Warning: function foo/0 is unused$", ErrorRE = "/system_test/erlc_SUITE_data/src/erl_test_missing_header.erl:" "[0-9]+: can't find include file \"missing.hrl\"$", DepRE_MMD = insert_before("_OK_", WarningRE, DepRE), DepRETarget_MMD = insert_before("_OK_", WarningRE, DepRETarget), DepREMP_MMD = insert_before("_OK_",WarningRE,DepREMP), DepREMissing_MMD = (insert_before("_OK_",ErrorRE,DepREMissing)-- ["_OK_"]) ++ ["_ERROR_"], CompRE = [WarningRE,"_OK_"], %% Test plain -MMD -M run(Config, Cmd, FileName, "-MMD -M", DepRE_MMD), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -MF File DepFile = filename:join(OutDir, "my.deps"), run(Config, Cmd, FileName, "-MMD -MF "++DepFile, CompRE), {ok,MFBin} = file:read_file(DepFile), verify_result(binary_to_list(MFBin)++["_OK_"], DepRE), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -MD run(Config, Cmd, FileName, "-MMD -MD", CompRE), MDFile = filename:join(OutDir, "erl_test_ok.Pbeam"), {ok,MFBin} = file:read_file(MDFile), file:delete(MDFile), %% used further down! true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -M -MT Target run(Config, Cmd, FileName, "-MMD -M -MT target", DepRETarget_MMD), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -MF File -MT Target TargetDepFile = filename:join(OutDir, "target.deps"), run(Config, Cmd, FileName, "-MMD -MF "++TargetDepFile++" -MT target", CompRE), {ok,TargetBin} = file:read_file(TargetDepFile), verify_result(binary_to_list(TargetBin)++["_OK_"], DepRETarget), file:delete(TargetDepFile), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -MD -MT Target run(Config, Cmd, FileName, "-MMD -MD -MT target", CompRE), TargetMDFile = filename:join(OutDir, "erl_test_ok.Pbeam"), {ok,TargetBin} = file:read_file(TargetMDFile), file:delete(TargetDepFile), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -M -MQ Target. (Note: Passing a $ on the command line %% portably for Unix and Windows is tricky, so we will just test %% that MQ works at all.) run(Config, Cmd, FileName, "-MMD -M -MQ target", DepRETarget_MMD), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -M -MP run(Config, Cmd, FileName, "-MMD -M -MP", DepREMP_MMD), true = exists(BeamFileName), file:delete(BeamFileName), %% Test -MMD -M -MG MissingHeader = filename:join(SrcDir, "erl_test_missing_header.erl"), run(Config, Cmd, MissingHeader, "-MMD -M -MG", DepREMissing_MMD), false = exists(BeamFileName), ok. %% Runs a command. run(Config, Cmd0, Name, Options, Expect) -> Cmd = Cmd0 ++ " " ++ Options ++ " " ++ Name, io:format("~ts", [Cmd]), Result = run_command(Config, Cmd), verify_result(Result, Expect). verify_result(Result, Expect) -> Messages = split(Result, [], []), io:format("Result: ~p", [Messages]), io:format("Expected: ~p", [Expect]), match_messages(Messages, Expect). %% insert What before Item, crash if Item is not found insert_before(Item, What, [Item|List]) -> [What,Item|List]; insert_before(Item, What, [Other|List]) -> [Other|insert_before(Item, What, List)]. split([$\n|Rest], Current, Lines) -> split(Rest, [], [lists:reverse(Current)|Lines]); split([$\r|Rest], Current, Lines) -> split(Rest, Current, Lines); split([Char|Rest], Current, Lines) -> split(Rest, [Char|Current], Lines); split([], [], Lines) -> lists:reverse(Lines); split([], Current, Lines) -> split([], [], [lists:reverse(Current)|Lines]). match_messages([Msg|Rest1], [Regexp|Rest2]) -> case re:run(Msg, Regexp, [{capture,none}, unicode]) of match -> ok; nomatch -> io:format("Not matching: ~s\n", [Msg]), io:format("Regexp : ~s\n", [Regexp]), ct:fail(message_mismatch) end, match_messages(Rest1, Rest2); match_messages([], [Expect|Rest]) -> ct:fail({too_few_messages, [Expect|Rest]}); match_messages([Msg|Rest], []) -> ct:fail({too_many_messages, [Msg|Rest]}); match_messages([], []) -> ok. get_cmd(Cfg) -> {SrcDir, IncDir, OutDir} = get_dirs(Cfg), Cmd = erlc() ++ " -I" ++ IncDir ++ " -o" ++ OutDir ++ " ", {SrcDir, OutDir, Cmd}. get_dirs(Cfg) -> DataDir = proplists:get_value(data_dir, Cfg), PrivDir = proplists:get_value(priv_dir, Cfg), SrcDir = filename:join(DataDir, "src"), IncDir = filename:join(DataDir, "include"), {SrcDir, IncDir, PrivDir}. exists(Name) -> filelib:is_file(Name). %% Runs the command using os:cmd/1. %% %% Returns the output from the command (as a list of characters with %% embedded newlines). The very last line will indicate the %% exit status of the command, where _OK_ means zero, and _ERROR_ %% a non-zero exit status. run_command(Config, Cmd) -> TmpDir = filename:join(proplists:get_value(priv_dir, Config), "tmp"), file:make_dir(TmpDir), {RunFile, Run, Script} = run_command(TmpDir, os:type(), Cmd), ok = file:write_file(filename:join(TmpDir, RunFile), unicode:characters_to_binary(Script)), os:cmd(Run). run_command(Dir, {win32, _}, Cmd) -> BatchFile = filename:join(Dir, "run.bat"), Run = re:replace(filename:rootname(BatchFile), "/", "\\", [global,{return,list}]), {BatchFile, Run, ["@echo off\r\n", "set ERLC_EMULATOR=", ct:get_progname(), "\r\n", Cmd, "\r\n", "if errorlevel 1 echo _ERROR_\r\n", "if not errorlevel 1 echo _OK_\r\n"]}; run_command(Dir, {unix, _}, Cmd) -> Name = filename:join(Dir, "run"), {Name, "/bin/sh " ++ Name, ["#!/bin/sh\n", "ERLC_EMULATOR='", ct:get_progname(), "'\n", "export ERLC_EMULATOR\n", Cmd, "\n", "case $? in\n", " 0) echo '_OK_';;\n", " *) echo '_ERROR_';;\n", "esac\n"]}; run_command(_Dir, Other, _Cmd) -> ct:fail("Don't know how to test exit code for ~p", [Other]).