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 }