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