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