1 /**
2    Integration tests.
3  */
4 module it;
5 
6 public import unit_threaded;
7 import unit_threaded.integration;
8 
9 version(Windows)
10         enum objectFileExtension = ".obj";
11 else
12         enum objectFileExtension = ".o";
13 
14 version(dpp2)
15     alias WIP2 = ShouldFail;
16 else
17     enum WIP2;
18 
19 
20 mixin template shouldCompile(D dCode) {
21     import std.traits: getUDAs;
22     enum udasC = getUDAs!(__traits(parent, {}), C);
23     void verify() {
24         .shouldCompile(udasC[0], dCode);
25     }
26 }
27 
28 string shouldCompile(in D dCode) {
29     return `mixin shouldCompile!(D(q{` ~ dCode.code ~ `})); verify;`;
30 }
31 
32 
33 /// C code
34 struct C {
35     string code;
36 }
37 
38 /// C++ code
39 struct Cpp {
40     string code;
41 }
42 
43 /// D code
44 struct D {
45     string code;
46 }
47 
48 struct RuntimeArgs {
49     string[] args;
50 }
51 
52 struct DFlags {
53     string[] flags;
54     this(string[] flags...) @safe pure nothrow {
55         this.flags = flags.dup;
56     }
57 }
58 
59 struct IncludeSandbox {
60 
61     alias sandbox this;
62 
63     Sandbox sandbox;
64 
65     static auto opCall() @safe {
66         IncludeSandbox ret;
67         ret.sandbox = Sandbox();
68         return ret;
69     }
70 
71     void run(string[] args...) @safe const {
72         import dpp.runtime.options: Options;
73         import dpp.runtime.app: dppRun = run;
74         import std.algorithm: map;
75         import std.array: array;
76         import std.process: environment;
77 
78         const baseLineArgs = [
79             "d++",
80             "--include-path",
81             sandboxPath,
82             "--compiler",
83             environment.get("DC", "dmd"),
84         ];
85         auto options = Options(baseLineArgs ~ args);
86         options.dppFileNames[] = options.dppFileNames.map!(a => sandbox.inSandboxPath(a)).array;
87 
88         dppRun(options);
89     }
90 
91     void runPreprocessOnly(in string[] args...) @safe const {
92         run(["--preprocess-only", "--keep-pre-cpp-files"] ~ args);
93         version(Windows) {
94             // Windows tests would sometimes fail saying the D modules
95             // don't exist... I didn't prove it, but my suspicion is
96             // just an async write hasn't completed yet. This sleep, while
97             // a filthy hack, worked consistently for me.
98             import core.thread : Thread, msecs;
99             () @trusted { Thread.sleep(500.msecs); }();
100         }
101     }
102 
103     void shouldCompile(string file = __FILE__, size_t line = __LINE__)
104                       (in string[] srcFiles...)
105         @safe const
106     {
107         shouldCompile!(file, line)(DFlags(), srcFiles);
108     }
109 
110     void shouldCompile(string file = __FILE__, size_t line = __LINE__)
111                       (in DFlags dflags, in string[] srcFiles...)
112         @safe const
113     {
114         try
115             sandbox.shouldSucceed!(file, line)([dCompiler, "-m64", "-o-", "-c"] ~ dflags.flags ~ srcFiles);
116         catch(Exception e)
117             adjustMessage(e, srcFiles);
118     }
119 
120 
121     void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
122                          (in string[] srcFiles...)
123         @safe const
124     {
125         try
126             sandbox.shouldFail!(file, line)([dCompiler, "-m64", "-o-", "-c"] ~ srcFiles);
127         catch(Exception e)
128             adjustMessage(e, srcFiles);
129     }
130 
131     void shouldCompileButNotLink(string file = __FILE__, size_t line = __LINE__)
132                                 (in string[] srcFiles...)
133         @safe const
134     {
135         try
136             sandbox.shouldSucceed!(file, line)([dCompiler, "-c", "-ofblob" ~ objectFileExtension] ~ srcFiles);
137         catch(Exception e)
138             adjustMessage(e, srcFiles);
139 
140         shouldFail(dCompiler, "-ofblob", "blob" ~ objectFileExtension);
141     }
142 
143     private void adjustMessage(Exception e, in string[] srcFiles) @safe const {
144         import std.algorithm: map;
145         import std.array: join;
146         import std.file: readText;
147         import std.string: splitLines;
148         import std.range: enumerate;
149         import std.format: format;
150 
151         throw new UnitTestException(
152             "\n\n" ~ e.msg ~ "\n\n" ~ srcFiles
153             .map!(a => a ~ ":\n----------\n" ~ readText(sandbox.inSandboxPath(a))
154                   .splitLines
155                   .enumerate(1)
156                   .map!(b => format!"%5d:   %s"(b[0], b[1]))
157                   .dropPreamble
158                   .join("\n")
159                 )
160             .join("\n\n"), e.file, e.line);
161     }
162 
163     private string includeLine(in string headerFileName) @safe pure nothrow const {
164         return `#include "` ~ inSandboxPath(headerFileName) ~ `"`;
165     }
166 
167     void writeHeaderAndApp(in string headerFileName, in string headerText, in D app) @safe const {
168         writeFile(headerFileName, headerText);
169         // take care of including the header and putting the D
170         // code in a function
171         const dCode =
172             includeLine(headerFileName) ~ "\n" ~
173             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
174         writeFile("app.dpp", dCode);
175     }
176 }
177 
178 private auto dropPreamble(R)(R lines) {
179     import dpp.runtime.app: preamble;
180     import std.array: array;
181     import std.range: walkLength, drop;
182     import std.string: splitLines;
183 
184     const length = lines.save.walkLength;
185     const preambleLength = preamble(false).splitLines.length + 1;
186 
187     return length > preambleLength ? lines.drop(preambleLength).array : lines.array;
188 }
189 
190 /**
191    Convenience function in the typical case that a test has a C
192    header and a D main file.
193 */
194 void shouldCompile(string file = __FILE__, size_t line = __LINE__)
195                   (in C header, in D app, in string[] cmdLineArgs = [])
196 {
197     shouldCompile!(file, line)("hdr.h", header.code, app, cmdLineArgs);
198 }
199 
200 /**
201    Convenience function in the typical case that a test has a C
202    header and a D main file.
203 */
204 void shouldCompile(string file = __FILE__, size_t line = __LINE__)
205                   (in Cpp header, in D app, in string[] cmdLineArgs = [])
206 {
207     shouldCompile!(file, line)("hdr.hpp", header.code, app, cmdLineArgs);
208 }
209 
210 
211 private void shouldCompile(string file = __FILE__, size_t line = __LINE__)
212                           (in string headerFileName,
213                            in string headerText,
214                            in D app,
215                            in string[] cmdLineArgs = [])
216 {
217     with(const IncludeSandbox()) {
218         writeHeaderAndApp(headerFileName, headerText, app);
219         runPreprocessOnly(cmdLineArgs ~ "app.dpp");
220         shouldCompile!(file, line)("app.d");
221     }
222 }
223 
224 void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
225                   (in C header, in D app)
226 {
227     with(const IncludeSandbox()) {
228         writeHeaderAndApp("hdr.h", header.code, app);
229         runPreprocessOnly("app.dpp");
230         shouldNotCompile!(file, line)("app.d");
231     }
232 }
233 
234 
235 alias shouldRun = shouldCompileAndRun;
236 
237 /**
238    Convenience function in the typical case that a test has a C
239    header and a D main file.
240 */
241 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
242                         (in C header, in C cSource, in D app, in RuntimeArgs args = RuntimeArgs())
243 {
244     shouldCompileAndRun!(file, line)("hdr.h", header.code, "c.c", cSource.code, app, args);
245 }
246 
247 /**
248    Convenience function in the typical case that a test has a C
249    header and a D main file.
250 */
251 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
252                         (in Cpp header, in Cpp cppSource, in D app, in RuntimeArgs args = RuntimeArgs())
253 {
254     shouldCompileAndRun!(file, line)("hdr.hpp", header.code, "cpp.cpp", cppSource.code, app, args);
255 }
256 
257 
258 private void shouldCompileAndRun
259     (string file = __FILE__, size_t line = __LINE__)
260     (
261         in string headerFileName,
262         in string headerText,
263         in string cSourceFileName,
264         in string cText, in D app,
265         in RuntimeArgs args = RuntimeArgs(),
266     )
267 {
268     import std.process: environment;
269 
270     with(const IncludeSandbox()) {
271         writeHeaderAndApp(headerFileName, headerText, app);
272         writeFile(cSourceFileName, includeLine(headerFileName) ~ cText);
273 
274         const isCpp = headerFileName == "hdr.hpp";
275         const compilerName = () {
276             if(environment.get("TRAVIS", "") == "")
277                 return isCpp ? "clang++" : "clang";
278             else
279                 return isCpp ? "g++" : "gcc";
280         }();
281         const compiler = compilerName;
282         const languageStandard =  isCpp ? "-std=c++17" : "-std=c11";
283         const outputFileName = "c" ~ objectFileExtension;
284 
285         version(Windows)
286             shouldSucceed(compiler, "-o", outputFileName, "-g", languageStandard, "-c", cSourceFileName);
287         else
288             shouldSucceed(compiler, "-fPIE", "-o", outputFileName, "-g", languageStandard, "-c", cSourceFileName);
289 
290         shouldExist(outputFileName);
291 
292         runPreprocessOnly("app.dpp");
293 
294         version(Windows)
295             // stdc++ is GNU-speak, on Windows, it uses the Microsoft lib
296             const string[] linkStdLib = [];
297         else
298             const linkStdLib = isCpp ? ["-L-lstdc++"] : [];
299 
300         try
301             shouldSucceed!(file, line)([dCompiler, "-m64", "-g", "app.d", "c" ~ objectFileExtension] ~ linkStdLib);
302         catch(Exception e)
303             adjustMessage(e, ["app.d"]);
304 
305         try
306             shouldSucceed!(file, line)(["./app"] ~ args.args);
307         catch(Exception e)
308             adjustMessage(e, ["app.d"]);
309     }
310 }
311 
312 
313 private string dCompiler() @safe {
314     import std.process: environment;
315     return environment.get("DC", "dmd");
316 }