1 module tests.support.DStepRunner; 2 3 import std.process : execute; 4 import std.traits : ReturnType; 5 6 version (linux) 7 version = OptionalGNUStep; 8 9 version (Windows) 10 version = OptionalGNUStep; 11 12 private alias TestRunDStepResult = ReturnType!execute; 13 14 auto testRunDStep( 15 string[] sourcePaths, 16 string[] arguments, 17 string[]* outputContents = null, 18 string* command = null, 19 string file = __FILE__, 20 size_t line = __LINE__) 21 { 22 import core.exception : AssertError; 23 24 import std.algorithm : canFind, map; 25 import std.file : exists, isFile, readText, rmdirRecurse; 26 import std.path : buildPath; 27 import std.range : join; 28 import std.array : empty; 29 30 import dstep.driver.Util : makeDefaultOutputFile; 31 32 version (OptionalGNUStep) 33 { 34 if (arguments.canFind("-ObjC") || arguments.canFind("--objective-c")) 35 { 36 auto extra = findExtraGNUStepPaths(file, line); 37 38 if (extra.empty) 39 throw new NoGNUStepException(); 40 else 41 arguments ~= extra; 42 } 43 } 44 45 foreach (sourcePath; sourcePaths) 46 assertFileExists(sourcePath, file, line); 47 48 string outputDir = namedTempDir("dstepUnitTest"); 49 scope(exit) rmdirRecurse(outputDir); 50 51 string[] outputPaths; 52 53 if (sourcePaths.length == 1) 54 { 55 outputPaths ~= buildPath(outputDir, 56 makeDefaultOutputFile(sourcePaths[0], false)); 57 } 58 else 59 { 60 foreach (sourcePath; sourcePaths) 61 outputPaths ~= buildPath(outputDir, 62 makeDefaultOutputFile(sourcePath, false)); 63 } 64 65 auto localCommand = ["./bin/dstep"] ~ sourcePaths ~ arguments; 66 67 if (outputPaths.length == 1) 68 localCommand ~= ["-o", outputPaths[0]]; 69 else 70 localCommand ~= ["-o", outputDir]; 71 72 if (command) 73 *command = join(localCommand, " "); 74 75 auto result = execute(localCommand); 76 77 if (outputContents) 78 outputContents.length = outputPaths.length; 79 80 foreach (i, outputPath; outputPaths) 81 { 82 if (!exists(outputPath) || !isFile(outputPath)) 83 throw new NoOutputFile(result, outputPath); 84 85 if (outputContents) 86 (*outputContents)[i] = readText(outputPath); 87 } 88 89 return result; 90 } 91 92 class NoOutputFile : object.Exception 93 { 94 TestRunDStepResult result; 95 string path; 96 97 this (TestRunDStepResult result, string path, string file = __FILE__, size_t line = __LINE__) 98 { 99 super(path, file, line); 100 this.result = result; 101 this.path = path; 102 } 103 } 104 105 class NoGNUStepException : object.Exception 106 { 107 this (string file = __FILE__, size_t line = __LINE__) 108 { 109 super("Cannot find GNUStep.", file, line); 110 } 111 } 112 113 private: 114 115 class NamedTempDirException : object.Exception 116 { 117 import std.format : format; 118 119 immutable string path; 120 121 this (string path, string file = __FILE__, size_t line = __LINE__) 122 { 123 this.path = path; 124 125 super( 126 format("Cannot create temporary directory \"%s\".", path), 127 file, 128 line 129 ); 130 } 131 } 132 133 void assertFileExists( 134 string expected, 135 string file = __FILE__, 136 size_t line = __LINE__) 137 { 138 import core.exception : AssertError; 139 140 import std.format : format; 141 142 import tests.support.Util : fileExists; 143 144 if (!fileExists(expected)) 145 { 146 auto message = format("File %s doesn't exist.", expected); 147 throw new AssertError(message, file, line); 148 } 149 } 150 151 version (Posix) 152 import core.sys.posix.stdlib : mkdtemp; 153 else 154 { 155 import core.sys.windows.objbase : CoCreateGuid; 156 import core.sys.windows.basetyps : GUID; 157 } 158 159 string namedTempDir(string prefix) 160 { 161 import std.file; 162 import std.path; 163 import std.format; 164 165 version (Posix) 166 { 167 static void randstr (char[] slice) 168 { 169 import std.random; 170 171 foreach (i; 0 .. slice.length) 172 slice[i] = uniform!("[]")('A', 'Z'); 173 } 174 175 string name = format("%sXXXXXXXXXXXXXXXX\0", prefix); 176 char[] path = buildPath(tempDir(), name).dup; 177 const size_t termAnd6XSize = 7; 178 179 immutable size_t begin = path.length - name.length + prefix.length; 180 181 randstr(path[begin .. $ - termAnd6XSize]); 182 183 char* result = mkdtemp(path.ptr); 184 185 path = path[0..$-1]; 186 187 if (result == null) 188 throw new NamedTempDirException(path.idup); 189 190 return path.idup; 191 } 192 else 193 { 194 static string createGUID() 195 { 196 static char toHex(uint x) 197 { 198 if (x < 10) 199 return cast(char) ('0' + x); 200 else 201 return cast(char) ('A' + x - 10); 202 } 203 204 GUID guid; 205 CoCreateGuid(&guid); 206 207 ubyte* data = cast(ubyte*)&guid; 208 char[32] result; 209 210 foreach (i; 0 .. 16) 211 { 212 result[i * 2 + 0] = toHex(data[i] & 0x0fu); 213 result[i * 2 + 1] = toHex(data[i] >> 16); 214 } 215 216 return result.idup; 217 } 218 219 string name = prefix ~ createGUID(); 220 string path = buildPath(tempDir(), name); 221 222 try 223 mkdirRecurse(path); 224 catch (FileException) 225 throw new NamedTempDirException(path); 226 227 return path; 228 } 229 } 230 231 string[] findExtraGNUStepPaths(string file, size_t line) 232 { 233 import std.stdio : stderr; 234 import std.format : format; 235 236 auto gnuStepPath = findGNUStepIncludePath(); 237 238 if (gnuStepPath == null) 239 { 240 auto message = "Unable to check the assertion. GNUstep couldn't be found."; 241 stderr.writeln(format("Warning@%s(%d): %s", file, line, message)); 242 return []; 243 } 244 245 auto ccIncludePaths = findCcIncludePaths(); 246 247 if (ccIncludePaths == null) 248 { 249 auto message = "Unable to check the assertion. cc include paths couldn't be found."; 250 stderr.writeln(format("Warning@%s(%d): %s", file, line, message)); 251 return []; 252 } 253 254 return ccIncludePaths ~ gnuStepPath; 255 } 256 257 string findGNUStepIncludePath() 258 { 259 import std.file : isDir, exists; 260 import std.format : format; 261 262 string path = "/usr/include/GNUstep"; 263 264 if (exists(path) && isDir(path)) 265 return format("-I%s", path); 266 else 267 return null; 268 } 269 270 string[] findCcIncludePaths() 271 { 272 import std.process : executeShell; 273 auto result = executeShell("cc -E -v - < /dev/null"); 274 275 if (result.status == 0) 276 return extractIncludePaths(result.output); 277 else 278 return null; 279 } 280 281 string[] extractIncludePaths(string output) 282 { 283 import std.algorithm.searching; 284 import std.algorithm.iteration; 285 import std.array : array; 286 import std..string; 287 288 string start = "#include <...> search starts here:"; 289 string stop = "End of search list."; 290 291 auto paths = output.findSplitAfter(start)[1] 292 .findSplitBefore(stop)[0].strip(); 293 auto args = map!(a => format("-I%s", a.strip()))(paths.splitLines()); 294 return paths.empty ? null : args.array; 295 }