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