1 /**
2    Integration tests.
3  */
4 module it;
5 
6 public import unit_threaded;
7 public import unit_threaded.integration;
8 
9 struct In {
10     string value;
11 }
12 
13 struct Out {
14     string value;
15 }
16 
17 /// C code
18 struct C {
19     string code;
20 }
21 
22 /// C++ code
23 struct Cpp {
24     string code;
25 }
26 
27 /// D code
28 struct D {
29     string code;
30 }
31 
32 struct RuntimeArgs {
33     string[] args;
34 }
35 
36 struct IncludeSandbox {
37 
38     alias sandbox this;
39 
40     Sandbox sandbox;
41 
42     static auto opCall() @safe {
43         IncludeSandbox ret;
44         ret.sandbox = Sandbox();
45         return ret;
46     }
47 
48     void run(string[] args...) @safe const {
49         import dpp.runtime.options: Options;
50         import dpp.runtime.app: realRun = run;
51         import std.algorithm: map;
52         import std.array: array;
53 
54         const baseLineArgs = [
55             "d++",
56             "--include-path",
57             sandboxPath
58         ];
59         auto options = Options(baseLineArgs ~ args);
60         options.dppFileNames[] = options.dppFileNames.map!(a => sandbox.inSandboxPath(a)).array;
61 
62         realRun(options);
63     }
64 
65     void runPreprocessOnly(string[] args...) @safe const {
66         import dpp.runtime.options: Options;
67         import dpp.runtime.app: realRun = run;
68         import std.algorithm: map;
69         import std.array: array;
70 
71         const baseLineArgs = [
72             "d++",
73             "--preprocess-only",
74             "--include-path",
75             sandboxPath
76         ];
77         auto options = Options(baseLineArgs ~ args);
78         options.dppFileNames[] = options.dppFileNames.map!(a => sandbox.inSandboxPath(a)).array;
79 
80         realRun(options);
81     }
82 
83     void shouldCompile(string file = __FILE__, size_t line = __LINE__)
84                       (in string[] srcFiles...)
85         @safe const
86     {
87         try
88             sandbox.shouldSucceed!(file, line)(["dmd", "-o-", "-c"] ~ srcFiles);
89         catch(Exception e) {
90             adjustMessage(e, srcFiles);
91         }
92     }
93 
94     void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
95                          (in string[] srcFiles...)
96         @safe const
97     {
98         try
99             sandbox.shouldFail!(file, line)(["dmd", "-o-", "-c"] ~ srcFiles);
100         catch(Exception e) {
101             adjustMessage(e, srcFiles);
102         }
103     }
104 
105     void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
106                             (in string[] srcFiles...)
107         @safe const
108     {
109         try
110             sandbox.shouldSucceed!(file, line)(["dmd", "-run"] ~ srcFiles);
111         catch(Exception e) {
112             adjustMessage(e, srcFiles);
113         }
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)(["dmd", "-c", "-ofblob.o"] ~ srcFiles);
122         catch(Exception e) {
123             adjustMessage(e, srcFiles);
124         }
125 
126         shouldFail("dmd", "-ofblob", "blob.o");
127     }
128 
129     void adjustMessage(Exception e, in string[] srcFiles) @safe const {
130         import std.algorithm: map;
131         import std.array: join;
132         import std.file: readText;
133         import std..string: splitLines;
134         import std.range: enumerate;
135         import std.format: format;
136 
137         throw new UnitTestException(
138             "\n\n" ~ e.msg ~ "\n\n" ~ srcFiles
139             .map!(a => a ~ ":\n----------\n" ~ readText(sandbox.inSandboxPath(a))
140                   .splitLines
141                   .enumerate(1)
142                   .map!(b => format!"%5d:   %s"(b[0], b[1]))
143                   .join("\n")
144                 )
145             .join("\n\n"), e.file, e.line);
146 
147     }
148 }
149 
150 /**
151    Convenience function in the typical case that a test has a C
152    header and a D main file.
153 */
154 void shouldCompile(string file = __FILE__, size_t line = __LINE__)
155                   (in C header, in D app)
156 {
157     with(const IncludeSandbox()) {
158         writeFile("hdr.h", header.code);
159         // take care of including the header and putting the D
160         // code in a function
161         const dCode = `#include "` ~ inSandboxPath("hdr.h") ~ `"` ~ "\n" ~
162             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
163 
164         writeFile("app.dpp", dCode);
165         runPreprocessOnly("app.dpp");
166         shouldCompile!(file, line)("app.d");
167     }
168 }
169 
170 /**
171    Convenience function in the typical case that a test has a C
172    header and a D main file.
173 */
174 void shouldCompile(string file = __FILE__, size_t line = __LINE__)
175                   (in Cpp header, in D app)
176 {
177     with(const IncludeSandbox()) {
178         writeFile("hdr.hpp", header.code);
179         // take care of including the header and putting the D
180         // code in a function
181         const dCode = `#include "` ~ inSandboxPath("hdr.hpp") ~ `"` ~ "\n" ~
182             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
183 
184         writeFile("app.dpp", dCode);
185         runPreprocessOnly("app.dpp");
186         shouldCompile!(file, line)("app.d");
187     }
188 }
189 
190 
191 void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
192                   (in C header, in D app)
193 {
194     with(const IncludeSandbox()) {
195         writeFile("hdr.h", header.code);
196         // take care of including the header and putting the D
197         // code in a function
198         const dCode = `#include "` ~ inSandboxPath("hdr.h") ~ `"` ~ "\n" ~
199             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
200 
201         writeFile("app.dpp", dCode);
202         runPreprocessOnly("app.dpp");
203         shouldNotCompile!(file, line)("app.d");
204     }
205 }
206 
207 alias shouldRun = shouldCompileAndRun;
208 
209 /**
210    Convenience function in the typical case that a test has a C
211    header and a D main file.
212 */
213 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
214                         (in C header, in C source, in D app, in RuntimeArgs args = RuntimeArgs())
215 {
216     import std.process: environment;
217 
218     with(const IncludeSandbox()) {
219         writeFile("hdr.h", header.code);
220         const includeLine = `#include "` ~ inSandboxPath("hdr.h") ~ `"` ~ "\n";
221         const cSource = includeLine ~ source.code;
222         writeFile("c.c", cSource);
223 
224         const compiler = "gcc";
225 
226         shouldSucceed(compiler, "-g", "-c", "c.c");
227 
228         // take care of including the header and putting the D
229         // code in a function
230         const dCode = includeLine ~
231             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
232 
233         writeFile("app.dpp", dCode);
234         runPreprocessOnly("app.dpp");
235 
236         try
237             shouldSucceed!(file, line)(["dmd", "app.d", "c.o"]);
238         catch(Exception e)
239             adjustMessage(e, ["app.d"]);
240 
241         try
242             shouldSucceed!(file, line)(["./app"] ~ args.args);
243         catch(Exception e)
244             adjustMessage(e, ["app.d"]);
245     }
246 }
247 
248 /**
249    Convenience function in the typical case that a test has a C
250    header and a D main file.
251 */
252 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
253                         (in Cpp header, in Cpp source, in D app, in RuntimeArgs args = RuntimeArgs())
254 {
255     import std.process: environment;
256 
257     with(const IncludeSandbox()) {
258         writeFile("hdr.hpp", header.code);
259         const includeLine = `#include "` ~ inSandboxPath("hdr.hpp") ~ `"` ~ "\n";
260         const cppSource = includeLine ~ source.code;
261         writeFile("cpp.cpp", cppSource);
262 
263         const compiler = environment.get("TRAVIS", "") == ""
264             ? "g++"
265             : "g++-7";
266 
267         shouldSucceed(compiler, "-g", "-std=c++17", "-c", "cpp.cpp");
268 
269         // take care of including the header and putting the D
270         // code in a function
271         const dCode = includeLine ~
272             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
273 
274         writeFile("app.dpp", dCode);
275         runPreprocessOnly("app.dpp");
276 
277         try
278             shouldSucceed!(file, line)(["dmd", "app.d", "cpp.o", "-L-lstdc++"]);
279         catch(Exception e)
280             adjustMessage(e, ["app.d"]);
281 
282         try
283             shouldSucceed!(file, line)(["./app"] ~ args.args);
284         catch(Exception e)
285             adjustMessage(e, ["app.d"]);
286     }
287 }