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 }