1 #!/usr/bin/env rdmd 2 /* 3 * Distributed under the Boost Software License, Version 1.0. 4 * (See accompanying file LICENSE_1_0.txt or copy at 5 * http://www.boost.org/LICENSE_1_0.txt) 6 */ 7 module rdmd_test; 8 9 /** 10 RDMD Test-suite. 11 12 Authors: Andrej Mitrovic 13 14 Note: 15 While `rdmd_test` can be run directly, it is recommended to run 16 it via the tools build scripts using the `make test_rdmd` target. 17 18 When running directly, pass the rdmd binary as the first argument. 19 */ 20 21 import std.algorithm; 22 import std.exception; 23 import std.file; 24 import std.getopt; 25 import std.path; 26 import std.process; 27 import std.range; 28 import std..string; 29 import std.stdio; 30 31 version (Posix) 32 { 33 enum objExt = ".o"; 34 enum binExt = ""; 35 enum libExt = ".a"; 36 } 37 else version (Windows) 38 { 39 enum objExt = ".obj"; 40 enum binExt = ".exe"; 41 enum libExt = ".lib"; 42 } 43 else 44 { 45 static assert(0, "Unsupported operating system."); 46 } 47 48 bool verbose = false; 49 50 int main(string[] args) 51 { 52 string defaultCompiler; // name of default compiler expected by rdmd 53 bool concurrencyTest; 54 string model = "64"; // build architecture for dmd 55 string testCompilerList; // e.g. "ldmd2,gdmd" (comma-separated list of compiler names) 56 57 auto helpInfo = getopt(args, 58 "rdmd-default-compiler", "[REQUIRED] default D compiler used by rdmd executable", &defaultCompiler, 59 "concurrency", "whether to perform the concurrency test cases", &concurrencyTest, 60 "m|model", "architecture to run the tests for [32 or 64]", &model, 61 "test-compilers", "comma-separated list of D compilers to test with rdmd", &testCompilerList, 62 "v|verbose", "verbose output", &verbose, 63 ); 64 65 void reportHelp(string errorMsg = null, string file = __FILE__, size_t line = __LINE__) 66 { 67 defaultGetoptPrinter("rdmd_test: a test suite for rdmd\n\n" ~ 68 "USAGE:\trdmd_test [OPTIONS] <rdmd_binary>\n", 69 helpInfo.options); 70 enforce(errorMsg is null, errorMsg, file, line); 71 } 72 73 if (helpInfo.helpWanted || args.length == 1) 74 { 75 reportHelp(); 76 return 1; 77 } 78 79 if (args.length > 2) 80 { 81 writefln("Error: too many non-option arguments, expected 1 but got %s", args.length - 1); 82 return 1; // fail 83 } 84 string rdmd = args[1]; // path to rdmd executable 85 86 if (rdmd.length == 0) 87 reportHelp("ERROR: missing required --rdmd flag"); 88 89 if (defaultCompiler.length == 0) 90 reportHelp("ERROR: missing required --rdmd-default-compiler flag"); 91 92 enforce(rdmd.exists, 93 format("rdmd executable path '%s' does not exist", rdmd)); 94 95 // copy rdmd executable to temp dir: this enables us to set 96 // up its execution environment with other features, e.g. a 97 // dummy fallback compiler 98 string rdmdApp = tempDir().buildPath("rdmd_app_") ~ binExt; 99 scope (exit) std.file.remove(rdmdApp); 100 copy(rdmd, rdmdApp, Yes.preserveAttributes); 101 102 runCompilerAgnosticTests(rdmdApp, defaultCompiler, model); 103 104 // if no explicit list of test compilers is set, 105 // use the default compiler expected by rdmd 106 if (testCompilerList is null) 107 testCompilerList = defaultCompiler; 108 109 // run the test suite for each specified test compiler 110 foreach (testCompiler; testCompilerList.split(',')) 111 { 112 // if compiler is a relative filename it must be converted 113 // to absolute because this test changes directories 114 if (testCompiler.canFind!isDirSeparator || testCompiler.exists) 115 testCompiler = buildNormalizedPath(testCompiler.absolutePath); 116 117 runTests(rdmdApp, testCompiler, model); 118 if (concurrencyTest) 119 runConcurrencyTest(rdmdApp, testCompiler, model); 120 } 121 122 return 0; 123 } 124 125 string compilerSwitch(string compiler) { return "--compiler=" ~ compiler; } 126 127 string modelSwitch(string model) { return "-m" ~ model; } 128 129 auto execute(T...)(T args) 130 { 131 import std.stdio : writefln; 132 if (verbose) 133 writefln("[execute] %s", args[0]); 134 return std.process.execute(args); 135 } 136 137 void runCompilerAgnosticTests(string rdmdApp, string defaultCompiler, string model) 138 { 139 /* Test help string output when no arguments passed. */ 140 auto res = execute([rdmdApp]); 141 enforce(res.status == 1, res.output); 142 enforce(res.output.canFind("Usage: rdmd [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...")); 143 144 /* Test --help. */ 145 res = execute([rdmdApp, "--help"]); 146 enforce(res.status == 0, res.output); 147 enforce(res.output.canFind("Usage: rdmd [RDMD AND DMD OPTIONS]... program [PROGRAM OPTIONS]...")); 148 149 string helpText = res.output; 150 151 // verify help text matches expected defaultCompiler 152 { 153 version (Windows) helpText = helpText.replace("\r\n", "\n"); 154 enum compilerHelpLine = " --compiler=comp use the specified compiler (e.g. gdmd) instead of "; 155 auto offset = helpText.indexOf(compilerHelpLine); 156 enforce(offset >= 0); 157 auto compilerInHelp = helpText[offset + compilerHelpLine.length .. $]; 158 compilerInHelp = compilerInHelp[0 .. compilerInHelp.indexOf('\n')]; 159 enforce(defaultCompiler.baseName == compilerInHelp, 160 "Expected to find " ~ compilerInHelp ~ " in help text, found " ~ defaultCompiler ~ " instead"); 161 } 162 163 /* Test that unsupported -o... options result in failure */ 164 res = execute([rdmdApp, "-o-"]); // valid option for dmd but unsupported by rdmd 165 enforce(res.status == 1, res.output); 166 enforce(res.output.canFind("Option -o- currently not supported by rdmd"), res.output); 167 168 res = execute([rdmdApp, "-o-foo"]); // should not be treated the same as -o- 169 enforce(res.status == 1, res.output); 170 enforce(res.output.canFind("Unrecognized option: o-foo"), res.output); 171 172 res = execute([rdmdApp, "-opbreak"]); // should not be treated like valid -op 173 enforce(res.status == 1, res.output); 174 enforce(res.output.canFind("Unrecognized option: opbreak"), res.output); 175 176 // run the fallback compiler test (this involves 177 // searching for the default compiler, so cannot 178 // be run with other test compilers) 179 runFallbackTest(rdmdApp, defaultCompiler, model); 180 } 181 182 auto rdmdArguments(string rdmdApp, string compiler, string model) 183 { 184 return [rdmdApp, compilerSwitch(compiler), modelSwitch(model)]; 185 } 186 187 void runTests(string rdmdApp, string compiler, string model) 188 { 189 // path to rdmd + common arguments (compiler, model) 190 auto rdmdArgs = rdmdArguments(rdmdApp, compiler, model); 191 192 /* Test --force. */ 193 string forceSrc = tempDir().buildPath("force_src_.d"); 194 std.file.write(forceSrc, `void main() { pragma(msg, "compile_force_src"); }`); 195 196 auto res = execute(rdmdArgs ~ [forceSrc]); 197 enforce(res.status == 0, res.output); 198 enforce(res.output.canFind("compile_force_src")); 199 200 res = execute(rdmdArgs ~ [forceSrc]); 201 enforce(res.status == 0, res.output); 202 enforce(!res.output.canFind("compile_force_src")); // second call will not re-compile 203 204 res = execute(rdmdArgs ~ ["--force", forceSrc]); 205 enforce(res.status == 0, res.output); 206 enforce(res.output.canFind("compile_force_src")); // force will re-compile 207 208 /* Test --build-only. */ 209 string failRuntime = tempDir().buildPath("fail_runtime_.d"); 210 std.file.write(failRuntime, "void main() { assert(0); }"); 211 212 res = execute(rdmdArgs ~ ["--force", "--build-only", failRuntime]); 213 enforce(res.status == 0, res.output); // only built, enforce(0) not called. 214 215 res = execute(rdmdArgs ~ ["--force", failRuntime]); 216 enforce(res.status == 1, res.output); // enforce(0) called, rdmd execution failed. 217 218 string failComptime = tempDir().buildPath("fail_comptime_.d"); 219 std.file.write(failComptime, "void main() { static assert(0); }"); 220 221 res = execute(rdmdArgs ~ ["--force", "--build-only", failComptime]); 222 enforce(res.status == 1, res.output); // building will fail for static enforce(0). 223 224 res = execute(rdmdArgs ~ ["--force", failComptime]); 225 enforce(res.status == 1, res.output); // ditto. 226 227 /* Test --chatty. */ 228 string voidMain = tempDir().buildPath("void_main_.d"); 229 std.file.write(voidMain, "void main() { }"); 230 231 res = execute(rdmdArgs ~ ["--force", "--chatty", voidMain]); 232 enforce(res.status == 0, res.output); 233 enforce(res.output.canFind("stat ")); // stat should be called. 234 235 /* Test --dry-run. */ 236 res = execute(rdmdArgs ~ ["--force", "--dry-run", failComptime]); 237 enforce(res.status == 0, res.output); // static enforce(0) not called since we did not build. 238 enforce(res.output.canFind("mkdirRecurse "), res.output); // --dry-run implies chatty 239 240 res = execute(rdmdArgs ~ ["--force", "--dry-run", "--build-only", failComptime]); 241 enforce(res.status == 0, res.output); // --build-only should not interfere with --dry-run 242 243 /* Test --eval. */ 244 res = execute(rdmdArgs ~ ["--force", "-de", "--eval=writeln(`eval_works`);"]); 245 enforce(res.status == 0, res.output); 246 enforce(res.output.canFind("eval_works")); // there could be a "DMD v2.xxx header in the output" 247 248 // compiler flags 249 res = execute(rdmdArgs ~ ["--force", "-debug", 250 "--eval=debug {} else assert(false);"]); 251 enforce(res.status == 0, res.output); 252 253 // vs program file 254 res = execute(rdmdArgs ~ ["--force", 255 "--eval=assert(true);", voidMain]); 256 enforce(res.status != 0); 257 enforce(res.output.canFind("Cannot have both --eval and a program file ('" ~ 258 voidMain ~ "').")); 259 260 /* Test --exclude. */ 261 string packFolder = tempDir().buildPath("dsubpack"); 262 if (packFolder.exists) packFolder.rmdirRecurse(); 263 packFolder.mkdirRecurse(); 264 scope (exit) packFolder.rmdirRecurse(); 265 266 string subModObj = packFolder.buildPath("submod") ~ objExt; 267 string subModSrc = packFolder.buildPath("submod.d"); 268 std.file.write(subModSrc, "module dsubpack.submod; void foo() { }"); 269 270 // build an object file out of the dependency 271 res = execute([compiler, modelSwitch(model), "-c", "-of" ~ subModObj, subModSrc]); 272 enforce(res.status == 0, res.output); 273 274 string subModUser = tempDir().buildPath("subModUser_.d"); 275 std.file.write(subModUser, "module subModUser_; import dsubpack.submod; void main() { foo(); }"); 276 277 res = execute(rdmdArgs ~ ["--force", "--exclude=dsubpack", subModUser]); 278 enforce(res.status == 1, res.output); // building without the dependency fails 279 280 res = execute(rdmdArgs ~ ["--force", "--exclude=dsubpack", subModObj, subModUser]); 281 enforce(res.status == 0, res.output); // building with the dependency succeeds 282 283 /* Test --include. */ 284 auto packFolder2 = tempDir().buildPath("std"); 285 if (packFolder2.exists) packFolder2.rmdirRecurse(); 286 packFolder2.mkdirRecurse(); 287 scope (exit) packFolder2.rmdirRecurse(); 288 289 string subModSrc2 = packFolder2.buildPath("foo.d"); 290 std.file.write(subModSrc2, "module std.foo; void foobar() { }"); 291 292 std.file.write(subModUser, "import std.foo; void main() { foobar(); }"); 293 294 res = execute(rdmdArgs ~ ["--force", subModUser]); 295 enforce(res.status == 1, res.output); // building without the --include fails 296 297 res = execute(rdmdArgs ~ ["--force", "--include=std", subModUser]); 298 enforce(res.status == 0, res.output); // building with the --include succeeds 299 300 /* Test --extra-file. */ 301 302 string extraFileDi = tempDir().buildPath("extraFile_.di"); 303 std.file.write(extraFileDi, "module extraFile_; void f();"); 304 string extraFileD = tempDir().buildPath("extraFile_.d"); 305 std.file.write(extraFileD, "module extraFile_; void f() { return; }"); 306 string extraFileMain = tempDir().buildPath("extraFileMain_.d"); 307 std.file.write(extraFileMain, 308 "module extraFileMain_; import extraFile_; void main() { f(); }"); 309 310 res = execute(rdmdArgs ~ ["--force", extraFileMain]); 311 enforce(res.status == 1, res.output); // undefined reference to f() 312 313 res = execute(rdmdArgs ~ ["--force", 314 "--extra-file=" ~ extraFileD, extraFileMain]); 315 enforce(res.status == 0, res.output); // now OK 316 317 /* Test --loop. */ 318 { 319 auto testLines = "foo\nbar\ndoo".split("\n"); 320 321 auto pipes = pipeProcess(rdmdArgs ~ ["--force", "--loop=writeln(line);"], Redirect.stdin | Redirect.stdout); 322 foreach (input; testLines) 323 pipes.stdin.writeln(input); 324 pipes.stdin.close(); 325 326 while (!testLines.empty) 327 { 328 auto line = pipes.stdout.readln.strip; 329 if (line.empty || line.startsWith("DMD v")) continue; // git-head header 330 enforce(line == testLines.front, "Expected %s, got %s".format(testLines.front, line)); 331 testLines.popFront; 332 } 333 auto status = pipes.pid.wait(); 334 enforce(status == 0); 335 } 336 337 // vs program file 338 res = execute(rdmdArgs ~ ["--force", 339 "--loop=assert(true);", voidMain]); 340 enforce(res.status != 0); 341 enforce(res.output.canFind("Cannot have both --loop and a program file ('" ~ 342 voidMain ~ "').")); 343 344 /* Test --main. */ 345 string noMain = tempDir().buildPath("no_main_.d"); 346 std.file.write(noMain, "module no_main_; void foo() { }"); 347 348 // test disabled: Optlink creates a dialog box here instead of erroring. 349 /+ res = execute([rdmdApp, " %s", noMain)); 350 enforce(res.status == 1, res.output); // main missing +/ 351 352 res = execute(rdmdArgs ~ ["--main", noMain]); 353 enforce(res.status == 0, res.output); // main added 354 355 string intMain = tempDir().buildPath("int_main_.d"); 356 std.file.write(intMain, "int main(string[] args) { return args.length; }"); 357 358 res = execute(rdmdArgs ~ ["--main", intMain]); 359 enforce(res.status == 1, res.output); // duplicate main 360 361 /* Test --makedepend. */ 362 363 string packRoot = packFolder.buildPath("../").buildNormalizedPath(); 364 365 string depMod = packRoot.buildPath("depMod_.d"); 366 std.file.write(depMod, "module depMod_; import dsubpack.submod; void main() { }"); 367 368 res = execute(rdmdArgs ~ ["-I" ~ packRoot, "--makedepend", 369 "-of" ~ depMod[0..$-2], depMod]); 370 371 import std.ascii : newline; 372 373 // simplistic checks 374 enforce(res.output.canFind(depMod[0..$-2] ~ ": \\" ~ newline)); 375 enforce(res.output.canFind(newline ~ " " ~ depMod ~ " \\" ~ newline)); 376 enforce(res.output.canFind(newline ~ " " ~ subModSrc)); 377 enforce(res.output.canFind(newline ~ subModSrc ~ ":" ~ newline)); 378 enforce(!res.output.canFind("\\" ~ newline ~ newline)); 379 380 /* Test --makedepfile. */ 381 382 string depModFail = packRoot.buildPath("depModFail_.d"); 383 std.file.write(depModFail, "module depMod_; import dsubpack.submod; void main() { assert(0); }"); 384 385 string depMak = packRoot.buildPath("depMak_.mak"); 386 res = execute(rdmdArgs ~ ["--force", "--build-only", 387 "-I" ~ packRoot, "--makedepfile=" ~ depMak, 388 "-of" ~ depModFail[0..$-2], depModFail]); 389 scope (exit) std.file.remove(depMak); 390 391 string output = std.file.readText(depMak); 392 393 // simplistic checks 394 enforce(output.canFind(depModFail[0..$-2] ~ ": \\" ~ newline)); 395 enforce(output.canFind(newline ~ " " ~ depModFail ~ " \\" ~ newline)); 396 enforce(output.canFind(newline ~ " " ~ subModSrc)); 397 enforce(output.canFind(newline ~ "" ~ subModSrc ~ ":" ~ newline)); 398 enforce(!output.canFind("\\" ~ newline ~ newline)); 399 enforce(res.status == 0, res.output); // only built, enforce(0) not called. 400 401 /* Test signal propagation through exit codes */ 402 403 version (Posix) 404 { 405 import core.sys.posix.signal; 406 string crashSrc = tempDir().buildPath("crash_src_.d"); 407 std.file.write(crashSrc, `void main() { int *p; *p = 0; }`); 408 res = execute(rdmdArgs ~ [crashSrc]); 409 enforce(res.status == -SIGSEGV, format("%s", res)); 410 } 411 412 /* -of doesn't append .exe on Windows: https://d.puremagic.com/issues/show_bug.cgi?id=12149 */ 413 414 version (Windows) 415 { 416 string outPath = tempDir().buildPath("test_of_app"); 417 string exePath = outPath ~ ".exe"; 418 res = execute(rdmdArgs ~ ["--build-only", "-of" ~ outPath, voidMain]); 419 enforce(exePath.exists(), exePath); 420 } 421 422 /* Current directory change should not trigger rebuild */ 423 424 res = execute(rdmdArgs ~ [forceSrc]); 425 enforce(res.status == 0, res.output); 426 enforce(!res.output.canFind("compile_force_src")); 427 428 { 429 auto cwd = getcwd(); 430 scope(exit) chdir(cwd); 431 chdir(tempDir); 432 433 res = execute(rdmdArgs ~ [forceSrc.baseName()]); 434 enforce(res.status == 0, res.output); 435 enforce(!res.output.canFind("compile_force_src")); 436 } 437 438 auto conflictDir = forceSrc.setExtension(".dir"); 439 if (exists(conflictDir)) 440 { 441 if (isFile(conflictDir)) 442 remove(conflictDir); 443 else 444 rmdirRecurse(conflictDir); 445 } 446 mkdir(conflictDir); 447 res = execute(rdmdArgs ~ ["-of" ~ conflictDir, forceSrc]); 448 enforce(res.status != 0, "-of set to a directory should fail"); 449 450 res = execute(rdmdArgs ~ ["-of=" ~ conflictDir, forceSrc]); 451 enforce(res.status != 0, "-of= set to a directory should fail"); 452 453 /* rdmd should force rebuild when --compiler changes: https://issues.dlang.org/show_bug.cgi?id=15031 */ 454 455 res = execute(rdmdArgs ~ [forceSrc]); 456 enforce(res.status == 0, res.output); 457 enforce(!res.output.canFind("compile_force_src")); 458 459 auto fullCompilerPath = environment["PATH"] 460 .splitter(pathSeparator) 461 .map!(dir => dir.buildPath(compiler ~ binExt)) 462 .filter!exists 463 .front; 464 465 res = execute([rdmdApp, "--compiler=" ~ fullCompilerPath, modelSwitch(model), forceSrc]); 466 enforce(res.status == 0, res.output ~ "\nCan't run with --compiler=" ~ fullCompilerPath); 467 468 // Create an empty temporary directory and clean it up when exiting scope 469 static struct TmpDir 470 { 471 string name; 472 this(string name) 473 { 474 this.name = name; 475 if (exists(name)) rmdirRecurse(name); 476 mkdir(name); 477 } 478 @disable this(this); 479 ~this() 480 { 481 version (Windows) 482 { 483 import core.thread; 484 Thread.sleep(100.msecs); // Hack around Windows locking the directory 485 } 486 rmdirRecurse(name); 487 } 488 alias name this; 489 } 490 491 /* tmpdir */ 492 { 493 res = execute(rdmdArgs ~ [forceSrc, "--build-only"]); 494 enforce(res.status == 0, res.output); 495 496 TmpDir tmpdir = "rdmdTest"; 497 res = execute(rdmdArgs ~ ["--tmpdir=" ~ tmpdir, forceSrc, "--build-only"]); 498 enforce(res.status == 0, res.output); 499 enforce(res.output.canFind("compile_force_src")); 500 } 501 502 /* RDMD fails at building a lib when the source is in a subdir: https://issues.dlang.org/show_bug.cgi?id=14296 */ 503 { 504 TmpDir srcDir = "rdmdTest"; 505 string srcName = srcDir.buildPath("test.d"); 506 std.file.write(srcName, `void fun() {}`); 507 if (exists("test" ~ libExt)) std.file.remove("test" ~ libExt); 508 509 res = execute(rdmdArgs ~ ["--build-only", "--force", "-lib", srcName]); 510 enforce(res.status == 0, res.output); 511 enforce(exists(srcDir.buildPath("test" ~ libExt))); 512 enforce(!exists("test" ~ libExt)); 513 } 514 515 // Test with -od 516 { 517 TmpDir srcDir = "rdmdTestSrc"; 518 TmpDir libDir = "rdmdTestLib"; 519 520 string srcName = srcDir.buildPath("test.d"); 521 std.file.write(srcName, `void fun() {}`); 522 523 res = execute(rdmdArgs ~ ["--build-only", "--force", "-lib", "-od" ~ libDir, srcName]); 524 enforce(res.status == 0, res.output); 525 enforce(exists(libDir.buildPath("test" ~ libExt))); 526 527 // test with -od= too 528 TmpDir altLibDir = "rdmdTestAltLib"; 529 res = execute(rdmdArgs ~ ["--build-only", "--force", "-lib", "-od=" ~ altLibDir, srcName]); 530 enforce(res.status == 0, res.output); 531 enforce(exists(altLibDir.buildPath("test" ~ libExt))); 532 } 533 534 // Test with -of 535 { 536 TmpDir srcDir = "rdmdTestSrc"; 537 TmpDir libDir = "rdmdTestLib"; 538 539 string srcName = srcDir.buildPath("test.d"); 540 std.file.write(srcName, `void fun() {}`); 541 string libName = libDir.buildPath("libtest" ~ libExt); 542 543 res = execute(rdmdArgs ~ ["--build-only", "--force", "-lib", "-of" ~ libName, srcName]); 544 enforce(res.status == 0, res.output); 545 enforce(exists(libName)); 546 547 // test that -of= works too 548 string altLibName = libDir.buildPath("altlibtest" ~ libExt); 549 550 res = execute(rdmdArgs ~ ["--build-only", "--force", "-lib", "-of=" ~ altLibName, srcName]); 551 enforce(res.status == 0, res.output); 552 enforce(exists(altLibName)); 553 } 554 555 /* rdmd --build-only --force -c main.d fails: ./main: No such file or directory: https://issues.dlang.org/show_bug.cgi?id=16962 */ 556 { 557 TmpDir srcDir = "rdmdTest"; 558 string srcName = srcDir.buildPath("test.d"); 559 std.file.write(srcName, `void main() {}`); 560 string objName = srcDir.buildPath("test" ~ objExt); 561 562 res = execute(rdmdArgs ~ ["--force", "-c", srcName]); 563 enforce(res.status == 0, res.output); 564 enforce(exists(objName)); 565 } 566 567 /* [REG2.072.0] pragma(lib) is broken with rdmd: https://issues.dlang.org/show_bug.cgi?id=16978 */ 568 /* GDC does not support `pragma(lib)`, so disable when test compiler is gdmd: https://issues.dlang.org/show_bug.cgi?id=18421 569 (this constraint can be removed once GDC support for `pragma(lib)` is implemented) */ 570 571 version (linux) 572 if (compiler.baseName != "gdmd") 573 {{ 574 TmpDir srcDir = "rdmdTest"; 575 string libSrcName = srcDir.buildPath("libfun.d"); 576 std.file.write(libSrcName, `extern(C) void fun() {}`); 577 578 res = execute(rdmdArgs ~ ["-lib", libSrcName]); 579 enforce(res.status == 0, res.output); 580 enforce(exists(srcDir.buildPath("libfun" ~ libExt))); 581 582 string mainSrcName = srcDir.buildPath("main.d"); 583 std.file.write(mainSrcName, `extern(C) void fun(); pragma(lib, "fun"); void main() { fun(); }`); 584 585 res = execute(rdmdArgs ~ ["-L-L" ~ srcDir, mainSrcName]); 586 enforce(res.status == 0, res.output); 587 }} 588 589 /* https://issues.dlang.org/show_bug.cgi?id=16966 */ 590 { 591 immutable voidMainExe = setExtension(voidMain, binExt); 592 res = execute(rdmdArgs ~ [voidMain]); 593 enforce(res.status == 0, res.output); 594 enforce(!exists(voidMainExe)); 595 res = execute(rdmdArgs ~ ["--build-only", voidMain]); 596 enforce(res.status == 0, res.output); 597 enforce(exists(voidMainExe)); 598 remove(voidMainExe); 599 } 600 601 /* https://issues.dlang.org/show_bug.cgi?id=17198 - rdmd does not recompile 602 when --extra-file is added */ 603 { 604 TmpDir srcDir = "rdmdTest"; 605 immutable string src1 = srcDir.buildPath("test.d"); 606 immutable string src2 = srcDir.buildPath("test2.d"); 607 std.file.write(src1, "int x = 1; int main() { return x; }"); 608 std.file.write(src2, "import test; static this() { x = 0; }"); 609 610 res = execute(rdmdArgs ~ [src1]); 611 enforce(res.status == 1, res.output); 612 613 res = execute(rdmdArgs ~ ["--extra-file=" ~ src2, src1]); 614 enforce(res.status == 0, res.output); 615 616 res = execute(rdmdArgs ~ [src1]); 617 enforce(res.status == 1, res.output); 618 } 619 620 version (Posix) 621 { 622 import std.conv : to; 623 auto makeVersion = execute(["make", "--version"]).output.splitLines()[0]; 624 if (!makeVersion.skipOver("GNU Make ")) 625 stderr.writeln("rdmd_test: Can't detect Make version or not GNU Make, skipping Make SHELL/SHELLFLAGS test"); 626 else if (makeVersion.split(".").map!(to!int).array < [3, 82]) 627 stderr.writefln("rdmd_test: Make version (%s) is too old, skipping Make SHELL/SHELLFLAGS test", makeVersion); 628 else 629 { 630 import std.format : format; 631 632 auto textOutput = tempDir().buildPath("rdmd_makefile_test.txt"); 633 if (exists(textOutput)) 634 { 635 remove(textOutput); 636 } 637 enum makefileFormatter = `.ONESHELL: 638 SHELL = %s 639 .SHELLFLAGS = %-(%s %) --eval 640 %s: 641 import std.file; 642 write("$@","hello world\n");`; 643 string makefileString = format!makefileFormatter(rdmdArgs[0], rdmdArgs[1 .. $], textOutput); 644 auto makefilePath = tempDir().buildPath("rdmd_makefile_test.mak"); 645 std.file.write(makefilePath, makefileString); 646 auto make = environment.get("MAKE") is null ? "make" : environment.get("MAKE"); 647 res = execute([make, "-f", makefilePath]); 648 enforce(res.status == 0, res.output); 649 enforce(std.file.read(textOutput) == "hello world\n"); 650 } 651 } 652 } 653 654 void runConcurrencyTest(string rdmdApp, string compiler, string model) 655 { 656 // path to rdmd + common arguments (compiler, model) 657 auto rdmdArgs = rdmdArguments(rdmdApp, compiler, model); 658 659 string sleep100 = tempDir().buildPath("delay_.d"); 660 std.file.write(sleep100, "void main() { import core.thread; Thread.sleep(100.msecs); }"); 661 auto argsVariants = 662 [ 663 rdmdArgs ~ [sleep100], 664 rdmdArgs ~ ["--force", sleep100], 665 ]; 666 import std.parallelism, std.range, std.random; 667 foreach (rnd; rndGen.parallel(1)) 668 { 669 try 670 { 671 auto args = argsVariants[rnd % $]; 672 auto res = execute(args); 673 enforce(res.status == 0, res.output); 674 } 675 catch (Exception e) 676 { 677 import std.stdio; 678 writeln(e); 679 break; 680 } 681 } 682 } 683 684 void runFallbackTest(string rdmdApp, string buildCompiler, string model) 685 { 686 /* https://issues.dlang.org/show_bug.cgi?id=11997 687 if an explicit --compiler flag is not provided, rdmd should 688 search its own binary path first when looking for the default 689 compiler (determined by the compiler used to build it) */ 690 string localDMD = buildPath(tempDir(), baseName(buildCompiler).setExtension(binExt)); 691 std.file.write(localDMD, ""); // An empty file avoids the "Not a valid 16-bit application" pop-up on Windows 692 scope(exit) std.file.remove(localDMD); 693 694 auto res = execute(rdmdApp ~ [modelSwitch(model), "--force", "--chatty", "--eval=writeln(`Compiler found.`);"]); 695 enforce(res.status == 1, res.output); 696 enforce(res.output.canFind(format(`spawn [%(%s%),`, localDMD.only)), localDMD ~ " would not have been executed"); 697 }