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