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 16 mixin template shouldCompile(D dCode) { 17 import std.traits: getUDAs; 18 enum udasC = getUDAs!(__traits(parent, {}), C); 19 void verify() { 20 .shouldCompile(udasC[0], dCode); 21 } 22 } 23 24 string shouldCompile(in D dCode) { 25 return `mixin shouldCompile!(D(q{` ~ dCode.code ~ `})); verify;`; 26 } 27 28 29 /// C code 30 struct C { 31 string code; 32 } 33 34 /// C++ code 35 struct Cpp { 36 string code; 37 } 38 39 /// D code 40 struct D { 41 string code; 42 } 43 44 struct RuntimeArgs { 45 string[] args; 46 } 47 48 struct IncludeSandbox { 49 50 alias sandbox this; 51 52 Sandbox sandbox; 53 54 static auto opCall() @safe { 55 IncludeSandbox ret; 56 ret.sandbox = Sandbox(); 57 return ret; 58 } 59 60 void run(string[] args...) @safe const { 61 import dpp.runtime.options: Options; 62 import dpp.runtime.app: dppRun = run; 63 import std.algorithm: map; 64 import std.array: array; 65 66 const baseLineArgs = [ 67 "d++", 68 "--include-path", 69 sandboxPath 70 ]; 71 auto options = Options(baseLineArgs ~ args); 72 options.dppFileNames[] = options.dppFileNames.map!(a => sandbox.inSandboxPath(a)).array; 73 74 dppRun(options); 75 } 76 77 void runPreprocessOnly(in string[] args...) @safe const { 78 run(["--preprocess-only", "--keep-pre-cpp-files"] ~ args); 79 } 80 81 void shouldCompile(string file = __FILE__, size_t line = __LINE__) 82 (in string[] srcFiles...) 83 @safe const 84 { 85 try 86 sandbox.shouldSucceed!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles); 87 catch(Exception e) 88 adjustMessage(e, srcFiles); 89 } 90 91 void shouldNotCompile(string file = __FILE__, size_t line = __LINE__) 92 (in string[] srcFiles...) 93 @safe const 94 { 95 try 96 sandbox.shouldFail!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles); 97 catch(Exception e) 98 adjustMessage(e, srcFiles); 99 } 100 101 void shouldCompileButNotLink(string file = __FILE__, size_t line = __LINE__) 102 (in string[] srcFiles...) 103 @safe const 104 { 105 try 106 sandbox.shouldSucceed!(file, line)([dCompiler, "-c", "-ofblob.o"] ~ srcFiles); 107 catch(Exception e) 108 adjustMessage(e, srcFiles); 109 110 shouldFail(dCompiler, "-ofblob", "blob.o"); 111 } 112 113 private void adjustMessage(Exception e, in string[] srcFiles) @safe const { 114 import std.algorithm: map; 115 import std.array: join; 116 import std.file: readText; 117 import std.string: splitLines; 118 import std.range: enumerate; 119 import std.format: format; 120 121 throw new UnitTestException( 122 "\n\n" ~ e.msg ~ "\n\n" ~ srcFiles 123 .map!(a => a ~ ":\n----------\n" ~ readText(sandbox.inSandboxPath(a)) 124 .splitLines 125 .enumerate(1) 126 .map!(b => format!"%5d: %s"(b[0], b[1])) 127 .dropPreamble 128 .join("\n") 129 ) 130 .join("\n\n"), e.file, e.line); 131 } 132 133 private string includeLine(in string headerFileName) @safe pure nothrow const { 134 return `#include "` ~ inSandboxPath(headerFileName) ~ `"`; 135 } 136 137 void writeHeaderAndApp(in string headerFileName, in string headerText, in D app) @safe const { 138 writeFile(headerFileName, headerText); 139 // take care of including the header and putting the D 140 // code in a function 141 const dCode = 142 includeLine(headerFileName) ~ "\n" ~ 143 `void main() {` ~ "\n" ~ app.code ~ "\n}\n"; 144 writeFile("app.dpp", dCode); 145 } 146 } 147 148 private auto dropPreamble(R)(R lines) { 149 import dpp.runtime.app: preamble; 150 import std.array: array; 151 import std.range: walkLength, drop; 152 import std.string: splitLines; 153 154 const length = lines.save.walkLength; 155 const preambleLength = preamble.splitLines.length + 1; 156 157 return length > preambleLength ? lines.drop(preambleLength).array : lines.array; 158 } 159 160 /** 161 Convenience function in the typical case that a test has a C 162 header and a D main file. 163 */ 164 void shouldCompile(string file = __FILE__, size_t line = __LINE__) 165 (in C header, in D app, in string[] cmdLineArgs = []) 166 { 167 shouldCompile!(file, line)("hdr.h", header.code, app, cmdLineArgs); 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, in string[] cmdLineArgs = []) 176 { 177 shouldCompile!(file, line)("hdr.hpp", header.code, app, cmdLineArgs); 178 } 179 180 181 private void shouldCompile(string file = __FILE__, size_t line = __LINE__) 182 (in string headerFileName, 183 in string headerText, 184 in D app, 185 in string[] cmdLineArgs = []) 186 { 187 with(const IncludeSandbox()) { 188 writeHeaderAndApp(headerFileName, headerText, app); 189 runPreprocessOnly(cmdLineArgs ~ "app.dpp"); 190 shouldCompile!(file, line)("app.d"); 191 } 192 } 193 194 void shouldNotCompile(string file = __FILE__, size_t line = __LINE__) 195 (in C header, in D app) 196 { 197 with(const IncludeSandbox()) { 198 writeHeaderAndApp("hdr.h", header.code, app); 199 runPreprocessOnly("app.dpp"); 200 shouldNotCompile!(file, line)("app.d"); 201 } 202 } 203 204 205 alias shouldRun = shouldCompileAndRun; 206 207 /** 208 Convenience function in the typical case that a test has a C 209 header and a D main file. 210 */ 211 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__) 212 (in C header, in C cSource, in D app, in RuntimeArgs args = RuntimeArgs()) 213 { 214 shouldCompileAndRun!(file, line)("hdr.h", header.code, "c.c", cSource.code, app, args); 215 } 216 217 /** 218 Convenience function in the typical case that a test has a C 219 header and a D main file. 220 */ 221 void shouldCompileAndRun(string file = __FILE__, size_t line = __LINE__) 222 (in Cpp header, in Cpp cppSource, in D app, in RuntimeArgs args = RuntimeArgs()) 223 { 224 shouldCompileAndRun!(file, line)("hdr.hpp", header.code, "cpp.cpp", cppSource.code, app, args); 225 } 226 227 228 private void shouldCompileAndRun 229 (string file = __FILE__, size_t line = __LINE__) 230 ( 231 in string headerFileName, 232 in string headerText, 233 in string cSourceFileName, 234 in string cText, in D app, 235 in RuntimeArgs args = RuntimeArgs(), 236 ) 237 { 238 import std.process: environment; 239 240 with(const IncludeSandbox()) { 241 writeHeaderAndApp(headerFileName, headerText, app); 242 writeFile(cSourceFileName, includeLine(headerFileName) ~ cText); 243 244 const isCpp = headerFileName == "hdr.hpp"; 245 const compilerName = () { 246 if(environment.get("TRAVIS", "") == "") 247 return isCpp ? "clang++" : "clang"; 248 else 249 return isCpp ? "g++" : "gcc"; 250 }(); 251 const compilerVersion = environment.get("TRAVIS", "") == "" ? "" : "-7"; 252 const compiler = compilerName ~ compilerVersion; 253 const languageStandard = isCpp ? "-std=c++17" : "-std=c11"; 254 255 shouldSucceed(compiler, "-o", "c.o", "-g", languageStandard, "-c", cSourceFileName); 256 257 runPreprocessOnly("app.dpp"); 258 259 const linkStdLib = isCpp ? ["-L-lstdc++"] : []; 260 261 try 262 shouldSucceed!(file, line)([dCompiler, "-g", "app.d", "c.o"] ~ linkStdLib); 263 catch(Exception e) 264 adjustMessage(e, ["app.d"]); 265 266 try 267 shouldSucceed!(file, line)(["./app"] ~ args.args); 268 catch(Exception e) 269 adjustMessage(e, ["app.d"]); 270 } 271 } 272 273 274 private string dCompiler() @safe { 275 import std.process: environment; 276 return environment.get("DC", "dmd"); 277 }