1 module test; 2 3 import std.process; 4 import std.stdio; 5 import std.file; 6 import std.path; 7 import std.algorithm; 8 import std..string; 9 import std.exception; 10 11 int main () 12 { 13 return TestRunner().run; 14 } 15 16 struct TestRunner 17 { 18 private string wd; 19 20 int run () 21 { 22 int result = 0; 23 auto matrix = setup(); 24 25 foreach (const clang ; matrix.clangs) 26 { 27 import std..string; 28 29 activate(clang); 30 31 auto output = execute(["./bin/dstep", "--clang-version"]); 32 33 writeln("Testing with ", strip(output.output)); 34 result += unitTest(); 35 result += libraryTest(); 36 stdout.flush(); 37 } 38 39 return result; 40 } 41 42 string workingDirectory () 43 { 44 if (wd.length) 45 return wd; 46 47 return wd = getcwd(); 48 } 49 50 auto setup () 51 { 52 auto matrix = ClangMatrix(workingDirectory, clangBasePath); 53 54 matrix.downloadAll; 55 matrix.extractAll; 56 57 return matrix; 58 } 59 60 string clangBasePath () 61 { 62 return buildNormalizedPath(workingDirectory, "clangs"); 63 } 64 65 void activate (const Clang clang) 66 { 67 std.stdio.write("Activating clang ", clang.version_); 68 69 version (Windows) 70 { 71 auto src = buildNormalizedPath(workingDirectory, clang.versionedLibclang); 72 auto dest = buildNormalizedPath(workingDirectory, clang.libclang); 73 74 if (exists(dest)) 75 remove(dest); 76 77 copy(src, dest); 78 79 auto staticSrc = buildNormalizedPath(workingDirectory, clang.staticVersionedLibclang); 80 auto staticDest = buildNormalizedPath(workingDirectory, clang.staticLibclang); 81 82 if (exists(staticDest)) 83 remove(staticDest); 84 85 copy(staticSrc, staticDest); 86 } 87 else 88 { 89 execute(["./configure", "--llvm-path", clang.llvmLibPath]); 90 } 91 92 build(); 93 94 writeln(" [DONE]"); 95 } 96 97 int unitTest () 98 { 99 writeln("Running unit tests "); 100 101 auto result = executeShell(dubShellCommand("test")); 102 103 if (result.status != 0) 104 writeln(result.output); 105 106 return result.status; 107 } 108 109 /** 110 Test that dstep can be used as a library by compiling a dependent 111 dub package 112 */ 113 int libraryTest () 114 { 115 const string[string] env; 116 const config = Config.none; 117 const maxOutput = size_t.max; 118 const workDir = "tests/functional/test_package"; 119 const result = executeShell(dubShellCommand("build"), 120 env, 121 config, 122 maxOutput, 123 workDir); 124 if (result.status != 0) 125 writeln(result.output); 126 127 return result.status; 128 } 129 130 void build () 131 { 132 try 133 { 134 auto result = executeShell(dubShellCommand("build")); 135 136 if (result.status != 0) 137 { 138 writeln(result.output); 139 throw new Exception("Failed to build DStep"); 140 } 141 } 142 catch(ProcessException) 143 { 144 throw new ProcessException("Failed to execute dub"); 145 } 146 } 147 } 148 149 150 private string dubShellCommand(string subCommand) @safe pure nothrow 151 { 152 return "dub " ~ subCommand ~ dubArch; 153 } 154 155 private string dubArch() @safe pure nothrow 156 { 157 version (Windows) 158 { 159 version (X86_64) 160 return " --arch=x86_64"; 161 else 162 return " --arch=x86_mscoff"; 163 } 164 else 165 { 166 return ""; 167 } 168 } 169 170 struct Clang 171 { 172 string version_; 173 string baseUrl; 174 string filename; 175 string basePath; 176 177 version (linux) 178 { 179 enum extension = ".so"; 180 enum prefix = "lib"; 181 } 182 183 else version (OSX) 184 { 185 enum extension = ".dylib"; 186 enum prefix = "lib"; 187 } 188 189 else version (Windows) 190 { 191 enum extension = ".dll"; 192 enum staticExtension = ".lib"; 193 enum prefix = "lib"; 194 } 195 196 else version (FreeBSD) 197 { 198 enum extension = ".so"; 199 enum prefix = "lib"; 200 } 201 202 else 203 static assert(false, "Unsupported platform"); 204 205 string libclang () const 206 { 207 return Clang.prefix ~ "clang" ~ Clang.extension; 208 } 209 210 version (Windows) 211 { 212 string staticLibclang () const 213 { 214 return Clang.prefix ~ "clang" ~ Clang.staticExtension; 215 } 216 } 217 218 string versionedLibclang () const 219 { 220 return Clang.prefix ~ "clang-" ~ version_ ~ Clang.extension; 221 } 222 223 version (Windows) 224 { 225 string staticVersionedLibclang () const 226 { 227 return Clang.prefix ~ "clang-" ~ version_ ~ Clang.staticExtension; 228 } 229 } 230 231 string archivePath () const 232 { 233 return buildNormalizedPath(basePath, filename); 234 } 235 236 string extractionPath() const 237 { 238 version (Posix) 239 return archivePath.stripExtension.stripExtension; 240 else 241 return buildNormalizedPath(basePath, "clang-" ~ version_); 242 } 243 244 string llvmLibPath() const 245 { 246 version (Posix) 247 enum libPath = "lib"; 248 else 249 enum libPath = "bin"; 250 251 return buildNormalizedPath(extractionPath, libPath); 252 } 253 } 254 255 struct ClangMatrix 256 { 257 private 258 { 259 string basePath; 260 string workingDirectory; 261 string clangPath_; 262 immutable Clang[] clangs; 263 } 264 265 this (string workingDirectory, string basePath) 266 { 267 this.workingDirectory = workingDirectory; 268 this.basePath = basePath; 269 clangs = getClangs(); 270 } 271 272 void downloadAll () 273 { 274 mkdirRecurse(basePath); 275 276 foreach (clang ; ClangMatrix.clangs) 277 { 278 stdout.flush(); 279 download(clang); 280 } 281 } 282 283 void extractAll () 284 { 285 foreach (clang ; ClangMatrix.clangs) 286 { 287 extractArchive(clang); 288 extractLibclang(clang); 289 extractStaticLibclang(clang); 290 stdout.flush(); 291 } 292 } 293 294 private: 295 296 void download (const ref Clang clang) 297 { 298 import std.file : write; 299 import HttpClient : getBinary; 300 301 auto dest = clang.archivePath; 302 303 if (exists(dest)) 304 return; 305 306 auto url = clang.baseUrl ~ clang.filename; 307 308 std.stdio.write("Downloading clang ", clang.version_); 309 stdout.flush(); 310 write(dest, getBinary(url)); 311 writeln(" [DONE]"); 312 } 313 314 void extractArchive (const ref Clang clang) 315 { 316 auto src = clang.archivePath; 317 auto dest = clang.extractionPath; 318 319 if (exists(dest)) 320 return; 321 322 std.stdio.write("Extracting clang ", clang.version_); 323 mkdirRecurse(dest); 324 325 std.typecons.Tuple!(int, "status", string, "output") result; 326 327 version (Posix) 328 { 329 result = execute(["tar", "--strip-components=1", "-C", dest, "-xf", src]); 330 } 331 else 332 { 333 try 334 { 335 result = execute(["7z", "x", src, "-y", format("-o%s", dest)]); 336 } 337 catch (ProcessException) 338 { 339 throw new ProcessException("Failed to execute 7z"); 340 } 341 } 342 343 if (result.status != 0) 344 throw new ProcessException("Failed to extract archive"); 345 346 writeln(" [DONE]"); 347 } 348 349 void extractLibclang (const ref Clang clang) 350 { 351 version (Windows) 352 { 353 auto src = buildNormalizedPath(clang.extractionPath, "bin", clang.libclang); 354 auto dest = buildNormalizedPath(workingDirectory, clang.versionedLibclang); 355 356 stdout.flush(); 357 358 copy(src, dest); 359 } 360 } 361 362 void extractStaticLibclang (const ref Clang clang) 363 { 364 version (Windows) 365 { 366 auto src = buildNormalizedPath(clang.extractionPath, "lib", clang.staticLibclang); 367 auto dest = buildNormalizedPath(workingDirectory, clang.staticVersionedLibclang); 368 369 stdout.flush(); 370 371 copy(src, dest); 372 } 373 } 374 375 immutable(Clang[]) getClangs () 376 { 377 import std.process; 378 379 version (Posix) 380 void unsupported() 381 { 382 throw new Exception("Current version of '" ~ System.update ~ "' is not supported"); 383 } 384 385 version (FreeBSD) 386 { 387 version (D_LP64) 388 enum filename = "clang+llvm-%1s-amd64-unknown-freebsd10.tar.xz"; 389 390 else 391 enum filename = "clang+llvm-%1s-i386-unknown-freebsd10.tar.xz"; 392 } 393 394 else version (linux) 395 { 396 string filename; 397 398 if (System.isUbuntu || System.isTravis) 399 { 400 version (D_LP64) 401 filename = "clang+llvm-%1s-x86_64-linux-gnu-ubuntu-14.04.tar.xz"; 402 else 403 unsupported(); 404 } 405 406 else if (System.isDebian) 407 { 408 version (D_LP64) 409 filename = "clang+llvm-%1s-x86_64-linux-gnu-debian8.tar.xz"; 410 else 411 unsupported(); 412 } 413 414 else if (System.isFedora) 415 { 416 version (D_LP64) 417 filename = "clang+llvm-%1s-x86_64-fedora23.tar.xz"; 418 else 419 filename = "clang+llvm-%1s-i686-fedora23.tar.xz"; 420 } 421 422 else 423 unsupported(); 424 } 425 426 else version (OSX) 427 { 428 version (D_LP64) 429 enum filename = "clang+llvm-%1s-x86_64-apple-darwin.tar.xz"; 430 431 else 432 static assert(false, "Only 64bit versions of OS X are supported"); 433 } 434 435 else version (Win32) 436 enum filename = "LLVM-%1s-win32.exe"; 437 438 else version (Win64) 439 enum filename = "LLVM-%1s-win64.exe"; 440 441 else 442 static assert(false, "Unsupported platform"); 443 444 enum defaultLLVMVersion = "4.0.0"; 445 446 auto llvmVersion = environment.get("LLVM_VERSION", defaultLLVMVersion); 447 auto baseUrl = format("http://releases.llvm.org/%1s/", llvmVersion); 448 auto filenameWithVersion = format(filename, llvmVersion); 449 450 return [Clang(llvmVersion, baseUrl, filenameWithVersion, basePath)]; 451 } 452 } 453 454 struct System 455 { 456 static: 457 458 version (D_LP64) 459 bool isTravis () 460 { 461 return environment.get("TRAVIS", "false") == "true"; 462 } 463 464 else 465 bool isTravis () 466 { 467 return false; 468 } 469 470 version (Posix): 471 472 import core.sys.posix.sys.utsname; 473 474 private 475 { 476 utsname data_; 477 string update_; 478 string nodename_; 479 } 480 481 bool isFedora () 482 { 483 return nodename.canFind("fedora"); 484 } 485 486 bool isUbuntu () 487 { 488 return nodename.canFind("ubuntu") || update.canFind("ubuntu"); 489 } 490 491 bool isDebian () 492 { 493 return nodename.canFind("debian"); 494 } 495 496 private utsname data() 497 { 498 import std.exception; 499 500 if (data_ != data_.init) 501 return data_; 502 503 errnoEnforce(!uname(&data_)); 504 return data_; 505 } 506 507 string update () 508 { 509 if (update_.length) 510 return update_; 511 512 return update_ = data.update.ptr.fromStringz.toLower.assumeUnique; 513 } 514 515 string nodename () 516 { 517 if (nodename_.length) 518 return nodename_; 519 520 return nodename_ = data.nodename.ptr.fromStringz.toLower.assumeUnique; 521 } 522 }