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