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