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