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         run(["--preprocess-only", "--keep-pre-cpp-files"] ~ args);
67     }
68 
69     void shouldCompile(string file = __FILE__, size_t line = __LINE__)
70                       (in string[] srcFiles...)
71         @safe const
72     {
73         try
74             sandbox.shouldSucceed!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles);
75         catch(Exception e)
76             adjustMessage(e, srcFiles);
77     }
78 
79     void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
80                          (in string[] srcFiles...)
81         @safe const
82     {
83         try
84             sandbox.shouldFail!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles);
85         catch(Exception e)
86             adjustMessage(e, srcFiles);
87     }
88 
89     void shouldCompileButNotLink(string file = __FILE__, size_t line = __LINE__)
90                                 (in string[] srcFiles...)
91         @safe const
92     {
93         try
94             sandbox.shouldSucceed!(file, line)([dCompiler, "-c", "-ofblob.o"] ~ srcFiles);
95         catch(Exception e)
96             adjustMessage(e, srcFiles);
97 
98         shouldFail(dCompiler, "-ofblob", "blob.o");
99     }
100 
101     private void adjustMessage(Exception e, in string[] srcFiles) @safe const {
102         import std.algorithm: map;
103         import std.array: join;
104         import std.file: readText;
105         import std..string: splitLines;
106         import std.range: enumerate;
107         import std.format: format;
108 
109         throw new UnitTestException(
110             "\n\n" ~ e.msg ~ "\n\n" ~ srcFiles
111             .map!(a => a ~ ":\n----------\n" ~ readText(sandbox.inSandboxPath(a))
112                   .splitLines
113                   .enumerate(1)
114                   .map!(b => format!"%5d:   %s"(b[0], b[1]))
115                   .join("\n")
116                 )
117             .join("\n\n"), e.file, e.line);
118     }
119 
120     private string includeLine(in string headerFileName) @safe pure nothrow const {
121         return `#include "` ~ inSandboxPath(headerFileName) ~ `"`;
122     }
123 
124     void writeHeaderAndApp(in string headerFileName, in string headerText, in D app) @safe const {
125         writeFile(headerFileName, headerText);
126         // take care of including the header and putting the D
127         // code in a function
128         const dCode =
129             includeLine(headerFileName) ~ "\n" ~
130             `void main() {` ~ "\n" ~ app.code ~ "\n}\n";
131         writeFile("app.dpp", dCode);
132     }
133 }
134 
135 /**
136    Convenience function in the typical case that a test has a C
137    header and a D main file.
138 */
139 void shouldCompile(string file = __FILE__, size_t line = __LINE__)
140                   (in C header, in D app)
141 {
142     shouldCompile!(file, line)("hdr.h", header.code, app);
143 }
144 
145 /**
146    Convenience function in the typical case that a test has a C
147    header and a D main file.
148 */
149 void shouldCompile(string file = __FILE__, size_t line = __LINE__)
150                   (in Cpp header, in D app)
151 {
152     shouldCompile!(file, line)("hdr.hpp", header.code, app);
153 }
154 
155 
156 private void shouldCompile(string file = __FILE__, size_t line = __LINE__)
157                           (in string headerFileName, in string headerText, in D app)
158 {
159     with(const IncludeSandbox()) {
160         writeHeaderAndApp(headerFileName, headerText, app);
161         runPreprocessOnly("app.dpp");
162         shouldCompile!(file, line)("app.d");
163     }
164 }
165 
166 void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
167                   (in C header, in D app)
168 {
169     with(const IncludeSandbox()) {
170         writeHeaderAndApp("hdr.h", header.code, app);
171         runPreprocessOnly("app.dpp");
172         shouldNotCompile!(file, line)("app.d");
173     }
174 }
175 
176 
177 alias shouldRun = shouldCompileAndRun;
178 
179 /**
180    Convenience function in the typical case that a test has a C
181    header and a D main file.
182 */
183 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
184                         (in C header, in C cSource, in D app, in RuntimeArgs args = RuntimeArgs())
185 {
186     shouldCompileAndRun!(file, line)("hdr.h", header.code, "c.c", cSource.code, app, args);
187 }
188 
189 /**
190    Convenience function in the typical case that a test has a C
191    header and a D main file.
192 */
193 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__)
194                         (in Cpp header, in Cpp cppSource, in D app, in RuntimeArgs args = RuntimeArgs())
195 {
196     shouldCompileAndRun!(file, line)("hdr.hpp", header.code, "cpp.cpp", cppSource.code, app, args);
197 }
198 
199 
200 private void shouldCompileAndRun
201     (string file = __FILE__, size_t line = __LINE__)
202     (
203         in string headerFileName,
204         in string headerText,
205         in string cSourceFileName,
206         in string cText, in D app,
207         in RuntimeArgs args = RuntimeArgs(),
208     )
209 {
210     import std.process: environment;
211 
212     with(const IncludeSandbox()) {
213         writeHeaderAndApp(headerFileName, headerText, app);
214         writeFile(cSourceFileName, includeLine(headerFileName) ~ cText);
215 
216         const isCpp = headerFileName == "hdr.hpp";
217         const compilerName = isCpp ? "g++" : "gcc";
218         const compilerVersion = environment.get("TRAVIS", "") == "" ? "" : "-7";
219         const compiler = compilerName ~ compilerVersion;
220         const languageStandard =  isCpp ? "-std=c++17" : "-std=c11";
221 
222         shouldSucceed(compiler, "-o", "c.o", "-g", languageStandard, "-c", cSourceFileName);
223 
224         runPreprocessOnly("app.dpp");
225 
226         const linkStdLib = isCpp ? ["-L-lstdc++"] : [];
227 
228         try
229             shouldSucceed!(file, line)([dCompiler, "-g", "app.d", "c.o"] ~ linkStdLib);
230         catch(Exception e)
231             adjustMessage(e, ["app.d"]);
232 
233         try
234             shouldSucceed!(file, line)(["./app"] ~ args.args);
235         catch(Exception e)
236             adjustMessage(e, ["app.d"]);
237     }
238 }
239 
240 
241 private string dCompiler() @safe {
242     import std.process: environment;
243     return environment.get("DC", "dmd");
244 }