1 module test;
2 
3 import std.process;
4 import std.file : rmdirRecurse;
5 
6 import Path = tango.io.Path;
7 
8 import mambo.core._;
9 
10 int main ()
11 {
12     return TestRunner().run;
13 }
14 
15 struct TestRunner
16 {
17     private string wd;
18 
19     int run ()
20     {
21         int result = 0;
22         auto matrix = setup();
23         activate(matrix.clangs.first);
24         build();
25 
26         foreach (const clang ; matrix.clangs)
27         {
28             activate(clang);
29             println("Testing with libclang version ", clang.version_);
30             result += test();
31         }
32 
33         return result;
34     }
35 
36     string workingDirectory ()
37     {
38         import tango.sys.Environment;
39 
40         if (wd.any)
41             return wd;
42 
43         return wd = Environment.cwd.assumeUnique;
44     }
45 
46     auto setup ()
47     {
48         auto matrix = ClangMatrix(workingDirectory, clangBasePath);
49         matrix.downloadAll;
50         matrix.extractAll;
51 
52         return matrix;
53     }
54 
55     string clangBasePath ()
56     {
57         return Path.join(workingDirectory, "clangs").assumeUnique;
58     }
59 
60     void activate (const Clang clang)
61     {
62         auto src = Path.join(workingDirectory, clang.versionedLibclang);
63         auto dest = Path.join(workingDirectory, clang.libclang);
64 
65         if (Path.exists(dest))
66             Path.remove(dest);
67 
68         Path.copy(src, dest);
69     }
70 
71     int test ()
72     {
73         auto result = execute("cucumber");
74 
75         if (result.status != 0)
76             println(result.output);
77 
78         return result.status;
79     }
80 
81     void build ()
82     {
83         auto result = executeShell("dub build");
84 
85         if (result.status != 0)
86         {
87             println(result.output);
88             throw new Exception("Failed to build DStep");
89         }
90     }
91 }
92 
93 struct Clang
94 {
95     string version_;
96     string baseUrl;
97     string filename;
98 
99     version (linux)
100     {
101         enum extension = ".so";
102         enum prefix = "lib";
103     }
104 
105     else version (OSX)
106     {
107         enum extension = ".dylib";
108         enum prefix = "lib";
109     }
110 
111     else version (Windows)
112     {
113         enum extension = ".dll";
114         enum prefix = "";
115     }
116 
117     else version (FreeBSD)
118     {
119         enum extension = ".so";
120         enum prefix = "lib";
121     }
122 
123     else
124         static assert(false, "Unsupported platform");
125 
126     string libclang () const
127     {
128         return Clang.prefix ~ "clang" ~ Clang.extension;
129     }
130 
131     string versionedLibclang () const
132     {
133         return Clang.prefix ~ "clang-" ~ version_ ~ Clang.extension;
134     }
135 }
136 
137 struct ClangMatrix
138 {
139     import Path = tango.io.Path;
140 
141     private
142     {
143         string basePath;
144         string workingDirectory;
145         string clangPath_;
146         immutable Clang[] clangs;
147     }
148 
149 
150     this (string workingDirectory, string basePath)
151     {
152         clangs = getClangs();
153         this.workingDirectory = workingDirectory;
154         this.basePath = basePath;
155     }
156 
157     void downloadAll ()
158     {
159         foreach (clang ; ClangMatrix.clangs)
160         {
161             if (libclangExists(clang))
162                 continue;
163 
164             println("Downloading clang ", clang.version_);
165             Path.createPath(basePath);
166             download(clang);
167         }
168     }
169 
170     void extractAll ()
171     {
172         foreach (clang ; ClangMatrix.clangs)
173         {
174             if (libclangExists(clang))
175                 continue;
176 
177             println("Extracting clang ", clang.version_);
178             extractArchive(clang);
179             extractLibclang(clang);
180             clean();
181         }
182     }
183 
184 private:
185 
186     bool libclangExists (const ref Clang clang)
187     {
188         auto libclangPath = Path.join(workingDirectory, clang.versionedLibclang);
189         return Path.exists(libclangPath);
190     }
191 
192     void download (const ref Clang clang)
193     {
194         auto url = clang.baseUrl ~ clang.filename;
195         auto dest = archivePath(clang.filename);
196 
197         if (!Path.exists(dest))
198             Http.download(url, dest);
199     }
200 
201     void extractArchive (const ref Clang clang)
202     {
203         auto src = archivePath(clang.filename);
204         auto dest = clangPath();
205         Path.createPath(dest);
206 
207         auto result = execute(["tar", "--strip-components=1", "-C", dest, "-xf", src]);
208 
209         if (result.status != 0)
210             throw new ProcessException("Failed to extract archive");
211     }
212 
213     string archivePath (string filename)
214     {
215         return Path.join(basePath, filename).assumeUnique;
216     }
217 
218     string clangPath ()
219     {
220         if (clangPath_.any)
221             return clangPath_;
222 
223         return clangPath_ = Path.join(basePath, "clang").assumeUnique;
224     }
225 
226     void extractLibclang (const ref Clang clang)
227     {
228         auto src = Path.join(clangPath, "lib", clang.libclang);
229         auto dest = Path.join(workingDirectory, clang.versionedLibclang);
230 
231         Path.copy(src, dest);
232     }
233 
234     void clean ()
235     {
236         rmdirRecurse(clangPath);
237     }
238 
239     immutable(Clang[]) getClangs ()
240     {
241         version (FreeBSD)
242         {
243             version (D_LP64)
244                 return [
245                     // Clang("3.7.1", "http://llvm.org/releases/3.7.1/", "clang+llvm-3.7.1-amd64-unknown-freebsd10.tar.xz"),
246                     // Clang("3.7.0", "http://llvm.org/releases/3.7.0/", "clang+llvm-3.7.0-amd64-unknown-freebsd10.tar.xz"),
247                     // Clang("3.6.2", "http://llvm.org/releases/3.6.2/", "clang+llvm-3.6.2-amd64-unknown-freebsd10.tar.xz"),
248                     // Clang("3.6.1", "http://llvm.org/releases/3.6.1/", "clang+llvm-3.6.1-amd64-unknown-freebsd10.tar.xz"),
249                     // Clang("3.6.0", "http://llvm.org/releases/3.6.0/", "clang+llvm-3.6.0-amd64-unknown-freebsd10.tar.xz"),
250                     // Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-amd64-unknown-freebsd10.tar.xz"),
251                     Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-amd64-unknown-freebsd9.2.tar.xz"),
252                     Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-amd64-freebsd9.tar.xz"),
253                     Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-amd64-freebsd9.tar.gz"),
254                     Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-amd64-freebsd9.tar.bz2")
255                 ];
256 
257             else
258                 return [
259                     // Clang("3.7.1", "http://llvm.org/releases/3.7.1/", "clang+llvm-3.7.1-i386-unknown-freebsd10.tar.xz"),
260                     // Clang("3.7.0", "http://llvm.org/releases/3.7.1/", "clang+llvm-3.7.0-i386-unknown-freebsd10.tar.xz"),
261                     // Clang("3.6.2", "http://llvm.org/releases/3.6.2/", "clang+llvm-3.6.2-i386-unknown-freebsd10.tar.xz"),
262                     // Clang("3.6.1", "http://llvm.org/releases/3.6.1/", "clang+llvm-3.6.1-i386-unknown-freebsd10.tar.xz"),
263                     // Clang("3.6.0", "http://llvm.org/releases/3.6.0/", "clang+llvm-3.6.0-i386-unknown-freebsd10.tar.xz"),
264                     // Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-i386-unknown-freebsd10.tar.xz"),
265                     Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-i386-unknown-freebsd9.2.tar.xz"),
266                     Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-i386-freebsd9.tar.xz"),
267                     Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-i386-freebsd9.tar.gz"),
268                     Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-i386-freebsd9.tar.bz2")
269                 ];
270         }
271 
272         else version (linux)
273         {
274             if (System.isTravis)
275             {
276                 return [
277                     // Clang("3.5.1", "http://llvm.org/releases/3.5.1/", "clang+llvm-3.5.1-x86_64-linux-gnu.tar.xz"),
278                     // Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz"),
279                     Clang("3.4.2", "http://llvm.org/releases/3.4.2/", "clang+llvm-3.4.2-x86_64-unknown-ubuntu12.04.xz"),
280                     Clang("3.4.1", "http://llvm.org/releases/3.4.1/", "clang+llvm-3.4.1-x86_64-unknown-ubuntu12.04.tar.xz"),
281                     Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-x86_64-unknown-ubuntu12.04.tar.xz"),
282                     Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-amd64-Ubuntu-12.04.2.tar.gz"),
283                     Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-x86_64-linux-ubuntu-12.04.tar.gz"),
284                     Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-x86_64-linux-ubuntu_12.04.tar.gz")
285                 ];
286             }
287 
288             else if (System.isUbuntu)
289             {
290                 version (D_LP64)
291                     return [
292                         Clang("3.5.1", "http://llvm.org/releases/3.5.1/", "clang+llvm-3.5.1-x86_64-linux-gnu.tar.xz"),
293                         Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz"),
294                         Clang("3.4.2", "http://llvm.org/releases/3.4.2/", "clang+llvm-3.4.2-x86_64-unknown-ubuntu12.04.xz"),
295                         Clang("3.4.1", "http://llvm.org/releases/3.4.1/", "clang+llvm-3.4.1-x86_64-unknown-ubuntu12.04.tar.xz"),
296                         Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-x86_64-unknown-ubuntu12.04.tar.xz"),
297                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-amd64-Ubuntu-12.04.2.tar.gz"),
298                         Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-x86_64-linux-ubuntu-12.04.tar.gz"),
299                         Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-x86_64-linux-ubuntu_12.04.tar.gz")
300                     ];
301                 else
302                     return [
303                         Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-x86-linux-ubuntu-12.04.tar.gz"),
304                         Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-x86-linux-ubuntu_12.04.tar.gz")
305                     ];
306             }
307 
308             else if (System.isDebian)
309             {
310                 version (D_LP64)
311                     return [
312                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-amd64-debian6.tar.bz2")
313                     ];
314                 else
315                     return [
316                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-i386-debian6.tar.bz2"),
317                     ];
318 
319             }
320 
321             else if (System.isFedora)
322             {
323                 version (D_LP64)
324                     return [
325                         Clang("3.5.1", "http://llvm.org/releases/3.5.1/", "clang+llvm-3.5.1-x86_64-fedora20.tar.xz"),
326                         Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-x86_64-fedora20.tar.xz"),
327                         Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-x86_64-fedora19.tar.gz"),
328                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-x86_64-fedora18.tar.bz2")
329                     ];
330                 else
331                     return [
332                         Clang("3.5.1", "http://llvm.org/releases/3.5.1/", "clang+llvm-3.5.1-i686-fedora20.tar.xz"),
333                         Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-i686-fedora20.tar.xz"),
334                         Clang("3.4.2", "http://llvm.org/releases/3.4.2/", "clang+llvm-3.4.2-i686-fedora20.xz"),
335                         Clang("3.4.1", "http://llvm.org/releases/3.4.1/", "clang+llvm-3.4.1-i686-fedora20.tar.xz"),
336                         Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-i686-fedora19.tar.gz"),
337                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-i686-fedora18.tar.bz2")
338                     ];
339             }
340 
341             else
342                 throw new Exception("Current Linux distribution '" ~ System.update ~ "' is not supported");
343         }
344 
345         else version (OSX)
346         {
347             version (D_LP64)
348             {
349                 if (System.isTravis)
350                     return [
351                         Clang("3.7.0", "http://llvm.org/releases/3.7.0/", "clang+llvm-3.7.0-x86_64-apple-darwin.tar.xz"),
352                         Clang("3.6.2", "http://llvm.org/releases/3.6.2/", "clang+llvm-3.6.2-x86_64-apple-darwin.tar.xz"),
353                         Clang("3.6.1", "http://llvm.org/releases/3.6.1/", "clang+llvm-3.6.1-x86_64-apple-darwin.tar.xz"),
354                         Clang("3.6.0", "http://llvm.org/releases/3.6.0/", "clang+llvm-3.6.0-x86_64-apple-darwin.tar.xz"),
355                         Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-macosx-apple-darwin.tar.xz"),
356                         // Clang("3.4.2", "http://llvm.org/releases/3.4.2/", "clang+llvm-3.4.2-x86_64-apple-darwin10.9.xz"),
357                         // Clang("3.4.1", "http://llvm.org/releases/3.4.1/", "clang+llvm-3.4.1-x86_64-apple-darwin10.9.tar.xz"),
358                         // Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-x86_64-apple-darwin10.9.tar.gz"),
359                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-x86_64-apple-darwin12.tar.gz"),
360                         Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-x86_64-apple-darwin11.tar.gz"),
361                         Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-x86_64-apple-darwin11.tar.gz")
362                     ];
363 
364                 else
365                     return [
366                         Clang("3.7.0", "http://llvm.org/releases/3.7.0/", "clang+llvm-3.7.0-x86_64-apple-darwin.tar.xz"),
367                         Clang("3.6.2", "http://llvm.org/releases/3.6.2/", "clang+llvm-3.6.2-x86_64-apple-darwin.tar.xz"),
368                         Clang("3.6.1", "http://llvm.org/releases/3.6.1/", "clang+llvm-3.6.1-x86_64-apple-darwin.tar.xz"),
369                         Clang("3.6.0", "http://llvm.org/releases/3.6.0/", "clang+llvm-3.6.0-x86_64-apple-darwin.tar.xz"),
370                         Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "clang+llvm-3.5.0-macosx-apple-darwin.tar.xz"),
371                         Clang("3.4.2", "http://llvm.org/releases/3.4.2/", "clang+llvm-3.4.2-x86_64-apple-darwin10.9.xz"),
372                         // Clang("3.4.1", "http://llvm.org/releases/3.4.1/", "clang+llvm-3.4.1-x86_64-apple-darwin10.9.tar.xz"),
373                         // Clang("3.4", "http://llvm.org/releases/3.4/", "clang+llvm-3.4-x86_64-apple-darwin10.9.tar.gz"),
374                         Clang("3.3", "http://llvm.org/releases/3.3/", "clang+llvm-3.3-x86_64-apple-darwin12.tar.gz"),
375                         Clang("3.2", "http://llvm.org/releases/3.2/", "clang+llvm-3.2-x86_64-apple-darwin11.tar.gz"),
376                         Clang("3.1", "http://llvm.org/releases/3.1/", "clang+llvm-3.1-x86_64-apple-darwin11.tar.gz")
377                     ];
378             }
379 
380             else
381                 static assert(false, "Only 64bit versions of OS X are supported");
382         }
383 
384         else version (Windows)
385         {
386             return [
387                 Clang("3.5.0", "http://llvm.org/releases/3.5.0/", "LLVM-3.5.0-win32.exe"),
388                 Clang("3.4.1", "http://llvm.org/releases/3.4.1/", "LLVM-3.4.1-win32.exe"),
389                 Clang("3.4", "http://llvm.org/releases/3.4/", "LLVM-3.4-win32.exe")
390             ];
391         }
392 
393         else
394             static assert(false, "Unsupported platform");
395     }
396 }
397 
398 struct System
399 {
400 static:
401 
402     version (D_LP64)
403         bool isTravis ()
404         {
405             return environment.get("TRAVIS", "false") == "true";
406         }
407 
408     else
409         bool isTravis ()
410         {
411             return false;
412         }
413 
414 version (linux):
415 
416     import core.sys.posix.sys.utsname;
417 
418     private
419     {
420         utsname data_;
421         string update_;
422         string nodename_;
423     }
424 
425     bool isFedora ()
426     {
427         return nodename.contains("fedora");
428     }
429 
430     bool isUbuntu ()
431     {
432         return nodename.contains("ubuntu");
433     }
434 
435     bool isDebian ()
436     {
437         return nodename.contains("debian");
438     }
439 
440     private utsname data()
441     {
442         import std.exception;
443 
444         if (data_ != data_.init)
445             return data_;
446 
447         errnoEnforce(!uname(&data_));
448         return data_;
449     }
450 
451     string update ()
452     {
453         if (update_.any)
454             return update_;
455 
456         return update_ = data.update.ptr.toString.toLower;
457     }
458 
459     string nodename ()
460     {
461         if (nodename_.any)
462             return nodename_;
463 
464         return nodename_ = data.nodename.ptr.toString.toLower;
465     }
466 }
467 
468 struct Http
469 {
470     import tango.io.device.File;
471     import tango.io.model.IConduit;
472     import tango.net.device.Socket;
473     import tango.net.http.HttpGet;
474     import tango.net.http.HttpConst;
475 
476 static:
477 
478     void download (string url, string destination, float timeout = 30f, ProgressHandler progress = new CliProgressHandler)
479     {
480         auto data = download(url, timeout, progress);
481         writeFile(data, destination);
482     }
483 
484     void[] download (string url, float timeout = 30f, ProgressHandler progress = new CliProgressHandler)
485     {
486         scope page = new HttpGet(url);
487         page.setTimeout(timeout);
488         auto buffer = page.open;
489 
490         checkPageStatus(page, url);
491 
492         auto contentLength = page.getResponseHeaders.getInt(HttpHeader.ContentLength);
493 
494         enum width = 40;
495         int bytesLeft = contentLength;
496         int chunkSize = bytesLeft / width;
497 
498         progress.start(contentLength, chunkSize, width);
499 
500         while (bytesLeft > 0)
501         {
502             buffer.load(chunkSize > bytesLeft ? bytesLeft : chunkSize);
503             bytesLeft -= chunkSize;
504             progress(bytesLeft);
505         }
506 
507         progress.end();
508 
509         return buffer.slice;
510     }
511 
512     bool exists (string url)
513     {
514         scope resource = new HttpGet(url);
515         resource.open;
516 
517         return resource.isResponseOK;
518     }
519 
520 private:
521 
522     void checkPageStatus (HttpGet page, string url)
523     {
524         import tango.core.Exception;
525 
526         if (page.getStatus == 404)
527             throw new IOException(format(`The resource with URL "{}" could not be found.`, url));
528 
529         else if (!page.isResponseOK)
530             throw new IOException(format(`An unexpected error occurred. The resource "{}" responded with the message "{}" and the status code {}.`, url, page.getResponse.getReason, page.getResponse.getStatus));
531     }
532 
533     void writeFile (void[] data, string filename)
534     {
535         scope file = new File(filename, File.WriteCreate);
536         file.write(data);
537     }
538 }
539 
540 abstract class ProgressHandler
541 {
542     void start (int length, int chunkSize, int width);
543     void opCall (int bytesLeft);
544     void end ();
545 }
546 
547 class CliProgressHandler : ProgressHandler
548 {
549     private
550     {
551         int num;
552         int width;
553         int chunkSize;
554         int contentLength;
555 
556         version (Posix)
557             enum
558             {
559                 clearLine = "\033[1K", // clear backwards
560                 saveCursor = "\0337",
561                 restoreCursor = "\0338"
562             }
563 
564         else
565             enum
566             {
567                 clearLine = "\r",
568                 saveCursor = "",
569                 restoreCursor = ""
570             }
571     }
572 
573     override void start (int contentLength, int chunkSize, int width)
574     {
575         this.chunkSize = chunkSize;
576         this.contentLength = contentLength;
577         this.width = width;
578         this.num = width;
579 
580         print(saveCursor);
581     }
582 
583     override void opCall (int bytesLeft)
584     {
585         int i = 0;
586 
587         print(clearLine ~ restoreCursor ~ saveCursor);
588         print("[");
589 
590         for ( ; i < (width - num); i++)
591             print("=");
592 
593         print(">");
594 
595         for ( ; i < width; i++)
596             print(" ");
597 
598         print("]");
599         print(format(" {}/{} KB", (contentLength - bytesLeft) / 1024, contentLength / 1024).assumeUnique);
600 
601         num--;
602     }
603 
604     override void end ()
605     {
606         println(restoreCursor);
607         println();
608     }
609 }