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 /// C code
16 struct C {
17     string code;
18 }
19 
20 /// C++ code
21 struct Cpp {
22     string code;
23 }
24 
25 /// D code
26 struct D {
27     string code;
28 }
29 
30 struct RuntimeArgs {
31     string[] args;
32 }
33 
34 struct IncludeSandbox {
35 
36     alias sandbox this;
37 
38     Sandbox sandbox;
39 
40     static auto opCall() @safe {
41         IncludeSandbox ret;
42         ret.sandbox = Sandbox();
43         return ret;
44     }
45 
46     void run(string[] args...) @safe const {
47         import dpp.runtime.options: Options;
48         import dpp.runtime.app: dppRun = run;
49         import std.algorithm: map;
50         import std.array: array;
51 
52         const baseLineArgs = [
53             "d++",
54             "--include-path",
55             sandboxPath
56         ];
57         auto options = Options(baseLineArgs ~ args);
58         options.dppFileNames[] = options.dppFileNames.map!(a => sandbox.inSandboxPath(a)).array;
59 
60         dppRun(options);
61     }
62 
63     void runPreprocessOnly(string[] args...) @safe const {
64         run(["--preprocess-only", "--keep-pre-cpp-files"] ~ args);
65     }
66 
67     void shouldCompile(string file = __FILE__, size_t line = __LINE__)
68                       (in string[] srcFiles...)
69         @safe const
70     {
71         try
72             sandbox.shouldSucceed!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles);
73         catch(Exception e)
74             adjustMessage(e, srcFiles);
75     }
76 
77     void shouldNotCompile(string file = __FILE__, size_t line = __LINE__)
78                          (in string[] srcFiles...)
79         @safe const
80     {
81         try
82             sandbox.shouldFail!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles);
83         catch(Exception e)
84             adjustMessage(e, srcFiles);
85     }
86 
87     void shouldCompileButNotLink(string file = __FILE__, size_t line = __LINE__)
88                                 (in string[] srcFiles...)
89         @safe const
90     {
91         try
92             sandbox.shouldSucceed!(file, line)([dCompiler, "-c", "-ofblob.o"] ~ srcFiles);
93         catch(Exception e)
94             adjustMessage(e, srcFiles);
95 
96         shouldFail(dCompiler, "-ofblob", "blob.o");
97     }
98 
99     private void adjustMessage(Exception e, in string[] srcFiles) @safe const {
100         import dpp.runtime.app: preamble;
101         import std.algorithm: map;
102         import std.array: join;
103         import std.file: readText;
104         import std..string: splitLines;
105         import std.range: enumerate, drop;
106         import std.format: format;
107 
108         throw new UnitTestException(
109             "\n\n" ~ e.msg ~ "\n\n" ~ srcFiles
110             .map!(a => a ~ ":\n----------\n" ~ readText(sandbox.inSandboxPath(a))
111                   .splitLines
112                   .enumerate(1)
113                   .map!(b => format!"%5d:   %s"(b[0], b[1]))
114                   .drop(preamble.splitLines.length + 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 }