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 run(["--preprocess-only", "--keep-pre-cpp-files"] ~ args); 67 } 68 69 void shouldCompile(string file = __FILE__, size_t line = __LINE__) 70 (in string[] srcFiles...) 71 @safe const 72 { 73 try 74 sandbox.shouldSucceed!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles); 75 catch(Exception e) 76 adjustMessage(e, srcFiles); 77 } 78 79 void shouldNotCompile(string file = __FILE__, size_t line = __LINE__) 80 (in string[] srcFiles...) 81 @safe const 82 { 83 try 84 sandbox.shouldFail!(file, line)([dCompiler, "-o-", "-c"] ~ srcFiles); 85 catch(Exception e) 86 adjustMessage(e, srcFiles); 87 } 88 89 void shouldCompileButNotLink(string file = __FILE__, size_t line = __LINE__) 90 (in string[] srcFiles...) 91 @safe const 92 { 93 try 94 sandbox.shouldSucceed!(file, line)([dCompiler, "-c", "-ofblob.o"] ~ srcFiles); 95 catch(Exception e) 96 adjustMessage(e, srcFiles); 97 98 shouldFail(dCompiler, "-ofblob", "blob.o"); 99 } 100 101 private void adjustMessage(Exception e, in string[] srcFiles) @safe const { 102 import std.algorithm: map; 103 import std.array: join; 104 import std.file: readText; 105 import std..string: splitLines; 106 import std.range: enumerate; 107 import std.format: format; 108 109 throw new UnitTestException( 110 "\n\n" ~ e.msg ~ "\n\n" ~ srcFiles 111 .map!(a => a ~ ":\n----------\n" ~ readText(sandbox.inSandboxPath(a)) 112 .splitLines 113 .enumerate(1) 114 .map!(b => format!"%5d: %s"(b[0], b[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 }