%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights 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]).
-include_lib("test_server/include/test_server.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[compile_erl, compile_yecc, compile_script, compile_mib,
good_citizen, deep_cwd, arg_overflow].
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) ->
?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
?line FileName = filename:join(SrcDir, "erl_test_ok.erl"),
%% By default, warnings are now turned on.
?line 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.
?line {module, erl_test_ok} = code:load_abs(filename:join(OutDir,
"erl_test_ok")),
?line 42 = erl_test_ok:shoe_size(#person{shoe_size=42}),
?line code:purge(erl_test_ok),
%% Try disabling warnings.
?line run(Config, Cmd, FileName, "-W0", ["_OK_"]),
%% Try treating warnings as errors.
?line run(Config, Cmd, FileName, "-Werror",
["compile: warnings being treated as errors\$",
"function foo/0 is unused\$",
"_ERROR_"]),
%% Check a bad file.
?line BadFile = filename:join(SrcDir, "erl_test_bad.erl"),
?line 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) ->
?line {SrcDir, _, OutDir} = get_dirs(Config),
?line Cmd = erlc() ++ " -o" ++ OutDir ++ " ",
?line FileName = filename:join(SrcDir, "yecc_test_ok.yrl"),
?line run(Config, Cmd, FileName, "-W0", ["_OK_"]),
?line true = exists(filename:join(OutDir, "yecc_test_ok.erl")),
?line BadFile = filename:join(SrcDir, "yecc_test_bad.yrl"),
?line run(Config, Cmd, BadFile, "-W0",
["rootsymbol form is not a nonterminal\$",
"undefined nonterminal: form\$",
"Nonterminals is missing\$",
"_ERROR_"]),
?line exists(filename:join(OutDir, "yecc_test_ok.erl")),
ok.
%% Test that compiling start scripts works.
compile_script(Config) when is_list(Config) ->
?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
?line FileName = filename:join(SrcDir, "start_ok.script"),
?line run(Config, Cmd, FileName, "", ["_OK_"]),
?line true = exists(filename:join(OutDir, "start_ok.boot")),
?line BadFile = filename:join(SrcDir, "start_bad.script"),
?line run(Config, Cmd, BadFile, "", ["syntax error before:", "_ERROR_"]),
ok.
%% Test that compiling SNMP mibs works.
compile_mib(Config) when is_list(Config) ->
?line {SrcDir, OutDir, Cmd} = get_cmd(Config),
?line FileName = filename:join(SrcDir, "GOOD-MIB.mib"),
?line run(Config, Cmd, FileName, "", ["_OK_"]),
?line Output = filename:join(OutDir, "GOOD-MIB.bin"),
?line true = exists(Output),
%% Try -W option.
?line ok = file:delete(Output),
?line run(Config, Cmd, FileName, "-W",
["_OK_"]),
?line true = exists(Output),
%% Try -W option and more verbose.
?line ok = file:delete(Output),
?line case test_server:os_type() of
{unix,_} ->
?line run(Config, Cmd, FileName, "-W +'{verbosity,info}'",
["\\[GOOD-MIB[.]mib\\]\\[INF\\]: No accessfunction for 'sysDescr' => using default",
"_OK_"]),
?line true = exists(Output),
?line ok = file:delete(Output);
_ -> ok %Don't bother -- too much work.
end,
%% Try a bad file.
?line BadFile = filename:join(SrcDir, "BAD-MIB.mib"),
?line run(Config, Cmd, BadFile, "",
["BAD-MIB.mib: 1: syntax error before: mibs\$",
"compilation_failed_ERROR_"]),
%% Make sure that no -I option works.
?line NewCmd = erlc() ++ " -o" ++ OutDir ++ " ",
?line run(Config, NewCmd, FileName, "", ["_OK_"]),
?line 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, _} ->
?line PrivDir = ?config(priv_dir, Config),
?line Answer = filename:join(PrivDir, "answer"),
?line Script = filename:join(PrivDir, "test_script"),
?line Test = filename:join(PrivDir, "test.erl"),
?line S = ["#! /bin/sh\n", "erlc ", Test, "\n",
"read reply\n", "echo $reply\n"],
?line ok = file:write_file(Script, S),
?line ok = file:write_file(Test, "-module(test).\n"),
?line Cmd = "echo y | sh " ++ Script ++ " > " ++ Answer,
?line os:cmd(Cmd),
?line {ok, Answer0} = file:read_file(Answer),
?line [$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 = ?config(priv_dir, Config),
deep_cwd_1(PrivDir);
_ ->
{skip, "Only a problem on Unix"}
end.
deep_cwd_1(PrivDir) ->
?line DeepDir0 = filename:join(PrivDir, lists:duplicate(128, $a)),
?line DeepDir = filename:join(DeepDir0, lists:duplicate(128, $b)),
?line ok = file:make_dir(DeepDir0),
?line ok = file:make_dir(DeepDir),
?line ok = file:set_cwd(DeepDir),
?line ok = file:write_file("test.erl", "-module(test).\n\n"),
?line io:format("~s\n", [os:cmd("erlc test.erl")]),
?line 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) ->
?line {SrcDir, _OutDir, Cmd} = get_cmd(Config),
?line FileName = filename:join(SrcDir, "erl_test_ok.erl"),
%% Each -D option will be expanded to three arguments when
%% invoking 'erl'.
?line NumDOptions = num_d_options(),
?line Args = lists:flatten([ ["-D", integer_to_list(N, 36), "=1 "] ||
N <- lists:seq(1, NumDOptions) ]),
?line 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;
{_,_} ->
12000
end.
erlc() ->
case os:find_executable("erlc") of
false ->
test_server:fail("Can't find erlc");
Erlc ->
Erlc
end.
%% Runs a command.
run(Config, Cmd0, Name, Options, Expect) ->
Cmd = Cmd0 ++ " " ++ Options ++ " " ++ Name,
io:format("~s", [Cmd]),
Result = run_command(Config, Cmd),
Messages = split(Result, [], []),
io:format("Result: ~p", [Messages]),
io:format("Expected: ~p", [Expect]),
match_messages(Messages, Expect).
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}]) of
match ->
ok;
nomatch ->
io:format("Not matching: ~s\n", [Msg]),
io:format("Regexp : ~s\n", [Regexp]),
test_server:fail(message_mismatch)
end,
match_messages(Rest1, Rest2);
match_messages([], [Expect|Rest]) ->
test_server:fail({too_few_messages, [Expect|Rest]});
match_messages([Msg|Rest], []) ->
test_server:fail({too_many_messages, [Msg|Rest]});
match_messages([], []) ->
ok.
get_cmd(Cfg) ->
?line {SrcDir, IncDir, OutDir} = get_dirs(Cfg),
?line Cmd = erlc() ++ " -I" ++ IncDir ++ " -o" ++ OutDir ++ " ",
{SrcDir, OutDir, Cmd}.
get_dirs(Cfg) ->
?line DataDir = ?config(data_dir, Cfg),
?line PrivDir = ?config(priv_dir, Cfg),
?line SrcDir = filename:join(DataDir, "src"),
?line 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(?config(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), 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=", atom_to_list(lib: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='", atom_to_list(lib: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) ->
M = io_lib:format("Don't know how to test exit code for ~p", [Other]),
test_server:fail(lists:flatten(M)).