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