1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 1997-2012. 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(erlc_SUITE).
   20: 
   21: %% Tests the erlc command by compiling various types of files.
   22: 
   23: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   24: 	 init_per_group/2,end_per_group/2, compile_erl/1,
   25: 	 compile_yecc/1, compile_script/1,
   26: 	 compile_mib/1, good_citizen/1, deep_cwd/1, arg_overflow/1,
   27: 	 make_dep_options/1]).
   28: 
   29: -include_lib("test_server/include/test_server.hrl").
   30: 
   31: suite() -> [{ct_hooks,[ts_install_cth]}].
   32: 
   33: all() -> 
   34:     [compile_erl, compile_yecc, compile_script, compile_mib,
   35:      good_citizen, deep_cwd, arg_overflow, make_dep_options].
   36: 
   37: groups() -> 
   38:     [].
   39: 
   40: init_per_suite(Config) ->
   41:     Config.
   42: 
   43: end_per_suite(_Config) ->
   44:     ok.
   45: 
   46: init_per_group(_GroupName, Config) ->
   47:     Config.
   48: 
   49: end_per_group(_GroupName, Config) ->
   50:     Config.
   51: 
   52: %% Copy from erlc_SUITE_data/include/erl_test.hrl.
   53: 
   54: -record(person, {name, shoe_size}).
   55: 
   56: %% Tests that compiling Erlang source code works.
   57: 
   58: compile_erl(Config) when is_list(Config) ->
   59:     ?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
   60:     ?line FileName = filename:join(SrcDir, "erl_test_ok.erl"),
   61: 
   62:     %% By default, warnings are now turned on.
   63:     ?line run(Config, Cmd, FileName, "",
   64: 	      ["Warning: function foo/0 is unused\$",
   65: 	       "_OK_"]),
   66: 
   67:     %% Test that the compiled file is where it should be,
   68:     %% and that it is runnable.
   69: 
   70:     ?line {module, erl_test_ok} = code:load_abs(filename:join(OutDir,
   71: 							      "erl_test_ok")),
   72:     ?line 42 = erl_test_ok:shoe_size(#person{shoe_size=42}),
   73:     ?line code:purge(erl_test_ok),
   74: 
   75:     %% Try disabling warnings.
   76: 
   77:     ?line run(Config, Cmd, FileName, "-W0", ["_OK_"]),
   78: 
   79:     %% Try treating warnings as errors.
   80: 
   81:     ?line run(Config, Cmd, FileName, "-Werror",
   82: 	      ["compile: warnings being treated as errors\$",
   83: 	       "function foo/0 is unused\$",
   84: 	       "_ERROR_"]),
   85: 
   86:     %% Check a bad file.
   87: 
   88:     ?line BadFile = filename:join(SrcDir, "erl_test_bad.erl"),
   89:     ?line run(Config, Cmd, BadFile, "", ["function non_existing/1 undefined\$",
   90: 				 "_ERROR_"]),
   91: 
   92:     ok.
   93: 
   94: %% Test that compiling yecc source code works.
   95: 
   96: compile_yecc(Config) when is_list(Config) ->
   97:     ?line {SrcDir, _, OutDir} = get_dirs(Config),
   98:     ?line Cmd = erlc() ++ " -o" ++ OutDir ++ " ",
   99:     ?line FileName = filename:join(SrcDir, "yecc_test_ok.yrl"),
  100:     ?line run(Config, Cmd, FileName, "-W0", ["_OK_"]),
  101:     ?line true = exists(filename:join(OutDir, "yecc_test_ok.erl")),
  102: 
  103:     ?line BadFile = filename:join(SrcDir, "yecc_test_bad.yrl"),
  104:     ?line run(Config, Cmd, BadFile, "-W0", 
  105: 	      ["rootsymbol form is not a nonterminal\$",
  106:                "undefined nonterminal: form\$",
  107:                "Nonterminals is missing\$",
  108:                "_ERROR_"]),
  109:     ?line exists(filename:join(OutDir, "yecc_test_ok.erl")),
  110: 
  111:     ok.
  112: 
  113: %% Test that compiling start scripts works.
  114: 
  115: compile_script(Config) when is_list(Config) ->
  116:     ?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
  117:     ?line FileName = filename:join(SrcDir, "start_ok.script"),
  118:     ?line run(Config, Cmd, FileName, "", ["_OK_"]),
  119:     ?line true = exists(filename:join(OutDir, "start_ok.boot")),
  120: 
  121:     ?line BadFile = filename:join(SrcDir, "start_bad.script"),
  122:     ?line run(Config, Cmd, BadFile, "", ["syntax error before:", "_ERROR_"]),
  123:     ok.
  124: 
  125: %% Test that compiling SNMP mibs works.
  126: 
  127: compile_mib(Config) when is_list(Config) ->
  128:     ?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
  129:     ?line FileName = filename:join(SrcDir, "GOOD-MIB.mib"),
  130:     ?line run(Config, Cmd, FileName, "", ["_OK_"]),
  131:     ?line Output = filename:join(OutDir, "GOOD-MIB.bin"),
  132:     ?line true = exists(Output),
  133: 
  134:     %% Try -W option.
  135: 
  136:     ?line ok = file:delete(Output),
  137:     ?line run(Config, Cmd, FileName, "-W",
  138: 	      ["_OK_"]),
  139:     ?line true = exists(Output),
  140: 
  141:     %% Try -W option and more verbose.
  142: 
  143:     ?line ok = file:delete(Output),
  144:     ?line case test_server:os_type() of
  145: 	      {unix,_} ->
  146: 		  ?line run(Config, Cmd, FileName, "-W +'{verbosity,info}'",
  147: 			    ["\\[GOOD-MIB[.]mib\\]\\[INF\\]: No accessfunction for 'sysDescr' => using default",
  148: 			     "_OK_"]),
  149: 		  ?line true = exists(Output),
  150: 		  ?line ok = file:delete(Output);
  151: 	      _ -> ok				%Don't bother -- too much work.
  152: 	  end,
  153: 
  154:     %% Try a bad file.
  155: 
  156:     ?line BadFile = filename:join(SrcDir, "BAD-MIB.mib"),
  157:     ?line run(Config, Cmd, BadFile, "",
  158: 	      ["BAD-MIB.mib: 1: syntax error before: mibs\$",
  159: 	       "compilation_failed_ERROR_"]),
  160: 
  161:     %% Make sure that no -I option works.
  162: 
  163:     ?line NewCmd = erlc() ++ " -o" ++ OutDir ++ " ",
  164:     ?line run(Config, NewCmd, FileName, "", ["_OK_"]),
  165:     ?line true = exists(Output),
  166: 
  167:     ok.
  168: 
  169: %% Checks that 'erlc' doesn't eat any input (important when called from a
  170: %% shell script with redirected input).
  171: good_citizen(Config) when is_list(Config) ->
  172:     case os:type() of
  173: 	{unix, _} ->
  174: 	    ?line PrivDir = ?config(priv_dir, Config),
  175: 	    ?line Answer = filename:join(PrivDir, "answer"),
  176: 	    ?line Script = filename:join(PrivDir, "test_script"),
  177: 	    ?line Test = filename:join(PrivDir, "test.erl"),
  178: 	    ?line S = ["#! /bin/sh\n", "erlc ", Test, "\n",
  179: 		       "read reply\n", "echo $reply\n"],
  180: 	    ?line ok = file:write_file(Script, S),
  181: 	    ?line ok = file:write_file(Test, "-module(test).\n"),
  182: 	    ?line Cmd = "echo y | sh " ++ Script ++ " > " ++ Answer,
  183: 	    ?line os:cmd(Cmd),
  184: 	    ?line {ok, Answer0} = file:read_file(Answer),
  185: 	    ?line [$y|_] = binary_to_list(Answer0),
  186: 	    ok;
  187: 	_ ->
  188: 	    {skip, "Unix specific"}
  189:     end.
  190: 
  191: %% Make sure that compiling an Erlang module deep down in
  192: %% in a directory with more than 255 characters works.
  193: deep_cwd(Config) when is_list(Config) ->
  194:     case os:type() of
  195: 	{unix, _} ->
  196: 	    PrivDir = ?config(priv_dir, Config),
  197: 	    deep_cwd_1(PrivDir);
  198: 	_ ->
  199: 	    {skip, "Only a problem on Unix"}
  200:     end.
  201: 
  202: deep_cwd_1(PrivDir) ->
  203:     ?line DeepDir0 = filename:join(PrivDir, lists:duplicate(128, $a)),
  204:     ?line DeepDir = filename:join(DeepDir0, lists:duplicate(128, $b)),
  205:     ?line ok = file:make_dir(DeepDir0),
  206:     ?line ok = file:make_dir(DeepDir),
  207:     ?line ok = file:set_cwd(DeepDir),
  208:     ?line ok = file:write_file("test.erl", "-module(test).\n\n"),
  209:     ?line io:format("~s\n", [os:cmd("erlc test.erl")]),
  210:     ?line true = filelib:is_file("test.beam"),
  211:     ok.
  212: 
  213: %% Test that a large number of command line switches does not
  214: %% overflow the argument buffer
  215: arg_overflow(Config) when is_list(Config) ->
  216:     ?line {SrcDir, _OutDir, Cmd} = get_cmd(Config),
  217:     ?line FileName = filename:join(SrcDir, "erl_test_ok.erl"),
  218:     %% Each -D option will be expanded to three arguments when
  219:     %% invoking 'erl'.
  220:     ?line NumDOptions = num_d_options(),
  221:     ?line Args = lists:flatten([ ["-D", integer_to_list(N, 36), "=1 "] ||
  222:             N <- lists:seq(1, NumDOptions) ]),
  223:     ?line run(Config, Cmd, FileName, Args,
  224: 	      ["Warning: function foo/0 is unused\$",
  225: 	       "_OK_"]),
  226:     ok.
  227: 
  228: num_d_options() ->
  229:     case {os:type(),os:version()} of
  230: 	{{win32,_},_} ->
  231: 	    %% The maximum size of a command line in the command
  232: 	    %% shell on Windows is 8191 characters.
  233: 	    %% Each -D option is expanded to "@dv NN 1", i.e.
  234: 	    %% 8 characters. (Numbers up to 1295 can be expressed
  235: 	    %% as two 36-base digits.)
  236: 	    1000;
  237: 	{{unix,linux},Version} when Version < {2,6,23} ->
  238: 	    %% On some older 64-bit versions of Linux, the maximum number
  239: 	    %% of arguments is 16383.
  240: 	    %% See: http://www.in-ulm.de/~mascheck/various/argmax/
  241: 	    5440;
  242: 	{{unix,darwin},{Major,_,_}} when Major >= 11 ->
  243: 	    %% "getconf ARG_MAX" still reports 262144 (as in previous
  244: 	    %% version of MacOS X), but the useful space seem to have
  245: 	    %% shrunk significantly (or possibly the number of arguments).
  246: 	    %% 7673
  247: 	    7500;
  248: 	{_,_} ->
  249: 	    12000
  250:     end.
  251: 
  252: erlc() ->
  253:     case os:find_executable("erlc") of
  254: 	false ->
  255: 	    test_server:fail("Can't find erlc");
  256: 	Erlc ->
  257: 	    "\"" ++ Erlc ++ "\""
  258:     end.
  259: 
  260: make_dep_options(Config) ->
  261:     {SrcDir,OutDir,Cmd} = get_cmd(Config),
  262:     FileName = filename:join(SrcDir, "erl_test_ok.erl"),
  263: 
  264: 
  265:     DepRE = ["/erl_test_ok[.]beam: \\\\$",
  266: 	     "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$",
  267: 	     "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$",
  268: 	     "_OK_"],
  269: 
  270:     DepRETarget =
  271: 	["^target: \\\\$",
  272: 	 "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$",
  273: 	 "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$",
  274: 	 "_OK_"],
  275: 
  276:     DepREMP =
  277: 	["/erl_test_ok[.]beam: \\\\$",
  278: 	 "/system_test/erlc_SUITE_data/src/erl_test_ok[.]erl \\\\$",
  279: 	 "/system_test/erlc_SUITE_data/include/erl_test[.]hrl$",
  280: 	 [],
  281: 	 "/system_test/erlc_SUITE_data/include/erl_test.hrl:$",
  282: 	 "_OK_"],
  283: 
  284:     DepREMissing =
  285: 	["/erl_test_missing_header[.]beam: \\\\$",
  286: 	 "/system_test/erlc_SUITE_data/src/erl_test_missing_header[.]erl \\\\$",
  287: 	 "/system_test/erlc_SUITE_data/include/erl_test[.]hrl \\\\$",
  288: 	 "missing.hrl$",
  289: 	 "_OK_"],
  290: 
  291:     %% Test plain -M
  292:     run(Config, Cmd, FileName, "-M", DepRE),
  293: 
  294:     %% Test -MF File
  295:     DepFile = filename:join(OutDir, "my.deps"),
  296:     run(Config, Cmd, FileName, "-MF "++DepFile, ["_OK_"]),
  297:     {ok,MFBin} = file:read_file(DepFile),
  298:     verify_result(binary_to_list(MFBin)++["_OK_"], DepRE),
  299: 
  300:     %% Test -MD
  301:     run(Config, Cmd, FileName, "-MD", ["_OK_"]),
  302:     MDFile = filename:join(OutDir, "erl_test_ok.Pbeam"),
  303:     {ok,MFBin} = file:read_file(MDFile),
  304: 
  305:     %% Test -M -MT Target
  306:     run(Config, Cmd, FileName, "-M -MT target", DepRETarget),
  307: 
  308:     %% Test -MF File -MT Target
  309:     TargetDepFile = filename:join(OutDir, "target.deps"),
  310:     run(Config, Cmd, FileName, "-MF "++TargetDepFile++" -MT target",
  311: 	["_OK_"]),
  312:     {ok,TargetBin} = file:read_file(TargetDepFile),
  313:     verify_result(binary_to_list(TargetBin)++["_OK_"], DepRETarget),
  314: 
  315:     %% Test -MD -MT Target
  316:     run(Config, Cmd, FileName, "-MD -MT target", ["_OK_"]),
  317:     TargetMDFile = filename:join(OutDir, "erl_test_ok.Pbeam"),
  318:     {ok,TargetBin} = file:read_file(TargetMDFile),
  319: 
  320:     %% Test -M -MQ Target. (Note: Passing a $ on the command line
  321:     %% portably for Unix and Windows is tricky, so we will just test
  322:     %% that MQ works at all.)
  323:     run(Config, Cmd, FileName, "-M -MQ target", DepRETarget),
  324: 
  325:     %% Test -M -MP
  326:     run(Config, Cmd, FileName, "-M -MP", DepREMP),
  327: 
  328:     %% Test -M -MG
  329:     MissingHeader = filename:join(SrcDir, "erl_test_missing_header.erl"),
  330:     run(Config, Cmd, MissingHeader, "-M -MG", DepREMissing),
  331:     ok.
  332: 
  333: %% Runs a command.
  334: 
  335: run(Config, Cmd0, Name, Options, Expect) ->
  336:     Cmd = Cmd0 ++ " " ++ Options ++ " " ++ Name,
  337:     io:format("~s", [Cmd]),
  338:     Result = run_command(Config, Cmd),
  339:     verify_result(Result, Expect).
  340: 
  341: verify_result(Result, Expect) ->
  342:     Messages = split(Result, [], []),
  343:     io:format("Result: ~p", [Messages]),
  344:     io:format("Expected: ~p", [Expect]),
  345:     match_messages(Messages, Expect).
  346: 
  347: split([$\n|Rest], Current, Lines) ->
  348:     split(Rest, [], [lists:reverse(Current)|Lines]);
  349: split([$\r|Rest], Current, Lines) ->
  350:     split(Rest, Current, Lines);
  351: split([Char|Rest], Current, Lines) ->
  352:     split(Rest, [Char|Current], Lines);
  353: split([], [], Lines) ->
  354:     lists:reverse(Lines);
  355: split([], Current, Lines) ->
  356:     split([], [], [lists:reverse(Current)|Lines]).
  357: 
  358: match_messages([Msg|Rest1], [Regexp|Rest2]) ->
  359:     case re:run(Msg, Regexp, [{capture,none}]) of
  360: 	match ->
  361: 	    ok;
  362: 	nomatch ->
  363: 	    io:format("Not matching: ~s\n", [Msg]),
  364: 	    io:format("Regexp      : ~s\n", [Regexp]),
  365: 	    test_server:fail(message_mismatch)
  366:     end,
  367:     match_messages(Rest1, Rest2);
  368: match_messages([], [Expect|Rest]) ->
  369:     test_server:fail({too_few_messages, [Expect|Rest]});
  370: match_messages([Msg|Rest], []) ->
  371:     test_server:fail({too_many_messages, [Msg|Rest]});
  372: match_messages([], []) ->
  373:     ok.
  374: 
  375: get_cmd(Cfg) ->
  376:     ?line {SrcDir, IncDir, OutDir} = get_dirs(Cfg),
  377:     ?line Cmd = erlc() ++ " -I" ++ IncDir ++ " -o" ++ OutDir ++ " ",
  378:     {SrcDir, OutDir, Cmd}.
  379: 
  380: get_dirs(Cfg) ->
  381:     ?line DataDir = ?config(data_dir, Cfg),
  382:     ?line PrivDir = ?config(priv_dir, Cfg),
  383:     ?line SrcDir = filename:join(DataDir, "src"),
  384:     ?line IncDir = filename:join(DataDir, "include"),
  385:     {SrcDir, IncDir, PrivDir}.
  386:     
  387: exists(Name) ->
  388:     filelib:is_file(Name).
  389: 
  390: %% Runs the command using os:cmd/1.
  391: %%
  392: %% Returns the output from the command (as a list of characters with
  393: %% embedded newlines).  The very last line will indicate the
  394: %% exit status of the command, where _OK_ means zero, and _ERROR_
  395: %% a non-zero exit status.
  396: 
  397: run_command(Config, Cmd) ->
  398:     TmpDir = filename:join(?config(priv_dir, Config), "tmp"),
  399:     file:make_dir(TmpDir),
  400:     {RunFile, Run, Script} = run_command(TmpDir, os:type(), Cmd),
  401:     ok = file:write_file(filename:join(TmpDir, RunFile), Script),
  402:     os:cmd(Run).
  403: 
  404: run_command(Dir, {win32, _}, Cmd) ->
  405:     BatchFile = filename:join(Dir, "run.bat"),
  406:     Run = re:replace(filename:rootname(BatchFile), "/", "\\",
  407: 		     [global,{return,list}]),
  408:     {BatchFile,
  409:      Run,
  410:      ["@echo off\r\n",
  411:       "set ERLC_EMULATOR=", atom_to_list(lib:progname()), "\r\n",
  412:       Cmd, "\r\n",
  413:       "if errorlevel 1 echo _ERROR_\r\n",
  414:       "if not errorlevel 1 echo _OK_\r\n"]};
  415: run_command(Dir, {unix, _}, Cmd) ->
  416:     Name = filename:join(Dir, "run"),
  417:     {Name,
  418:      "/bin/sh " ++ Name,
  419:      ["#!/bin/sh\n",
  420:       "ERLC_EMULATOR='", atom_to_list(lib:progname()), "'\n",
  421:       "export ERLC_EMULATOR\n",
  422:       Cmd, "\n",
  423:       "case $? in\n",
  424:       "  0) echo '_OK_';;\n",
  425:       "  *) echo '_ERROR_';;\n",
  426:       "esac\n"]};
  427: run_command(_Dir, Other, _Cmd) ->
  428:     M = io_lib:format("Don't know how to test exit code for ~p", [Other]),
  429:     test_server:fail(lists:flatten(M)).