1 module tests.it; 2 3 public import reggae; 4 public import unit_threaded; 5 public import std.path; 6 7 immutable string origPath; 8 9 shared static this() { 10 import std.file; 11 import std.path; 12 import std.algorithm; 13 import std.range; 14 import std.stdio: writeln; 15 16 auto paths = [".", ".."].map!(a => buildNormalizedPath(getcwd, a)) 17 .find!(a => buildNormalizedPath(a, "dub.json").exists); 18 assert(!paths.empty, "Error: Cannot find reggae top dir using dub.json"); 19 origPath = paths.front.absolutePath; 20 21 if(testsPath.exists) { 22 writelnUt("[IT] Removing old test path ", testsPath); 23 foreach(entry; dirEntries(testsPath, SpanMode.shallow)) { 24 if(isDir(entry.name)) { 25 rmdirRecurse(entry); 26 } 27 } 28 } 29 30 writelnUt("[IT] Creating new test path ", testsPath); 31 mkdirRecurse(testsPath); 32 33 buildDCompile(); 34 } 35 36 private void buildDCompile() { 37 import std.meta: aliasSeqOf; 38 import std.exception: enforce; 39 import std.conv: text; 40 import std.stdio: writeln; 41 import std.algorithm: any; 42 import std.file: exists; 43 import std.path: buildPath; 44 import std.process: execute, Config; 45 import std.array: join; 46 import reggae.file; 47 48 enum fileNames = ["dcompile.d", "dependencies.d"]; 49 50 const exeName = buildPath("tmp", "dcompile"); 51 immutable needToRecompile = 52 !exeName.exists || 53 fileNames. 54 any!(a => buildPath(origPath, "payload", "reggae", a). 55 newerThan(buildPath(testsPath, a))); 56 57 if(!needToRecompile) 58 return; 59 60 writeln("[IT] Building dcompile"); 61 62 foreach(fileName; aliasSeqOf!fileNames) { 63 writeFile!fileName; 64 } 65 66 enum args = ["dmd", "-ofdcompile"] ~ fileNames; 67 const string[string] env = null; 68 Config config = Config.none; 69 size_t maxOutput = size_t.max; 70 const workDir = testsPath; 71 72 immutable res = execute(args, env, config, maxOutput, workDir); 73 enforce(res.status == 0, text("Could not execute '", args.join(" "), "':\n", res.output)); 74 } 75 76 private void writeFile(string fileName)() { 77 import std.stdio; 78 import std.path; 79 auto file = File(buildPath(testsPath, fileName), "w"); 80 file.write(import(fileName)); 81 } 82 83 84 string testsPath() @safe { 85 import std.file; 86 import std.path; 87 return buildNormalizedPath(origPath, "tmp"); 88 } 89 90 91 string inOrigPath(T...)(T parts) { 92 return inPath(origPath, parts); 93 } 94 95 string inPath(T...)(in string path, T parts) { 96 import std.path; 97 return buildPath(path, parts).absolutePath; 98 } 99 100 string inPath(T...)(in Options options, T parts) { 101 return inPath(options.workingDir, parts); 102 } 103 104 105 string projectPath(in string name) { 106 import std.path; 107 return inOrigPath("tests", "projects", name); 108 } 109 110 extern(C) char* mkdtemp(char*); 111 112 string newTestDir() { 113 import std.conv; 114 import std.path; 115 import std.algorithm; 116 117 char[100] template_; 118 std.algorithm.copy(buildPath(testsPath, "XXXXXX") ~ '\0', template_[]); 119 auto ret = mkdtemp(&template_[0]).to!string; 120 121 return ret.absolutePath; 122 } 123 124 Options testOptions(string[] args) { 125 import reggae.config: setOptions; 126 auto options = getOptions(["reggae", "-C", newTestDir] ~ args); 127 setOptions(options); 128 return options; 129 } 130 131 Options _testProjectOptions(in string backend, in string projectName) { 132 return testOptions(["-b", backend, projectPath(projectName)]); 133 } 134 135 Options _testProjectOptions(in string projectName) { 136 return testOptions(["-b", getValue!string, projectPath(projectName)]); 137 } 138 139 Options _testProjectOptions(string module_)() { 140 import std.string; 141 return _testProjectOptions(module_.split(".")[0]); 142 } 143 144 Options _testProjectOptions(string module_)(string backend) { 145 import std.string; 146 return _testProjectOptions(backend, module_.split(".")[0]); 147 } 148 149 150 // used to change files and cause a rebuild 151 void overwrite(in Options options, in string fileName, in string newContents) { 152 import core.thread; 153 import std.stdio; 154 import std.path; 155 156 // ninja has problems with timestamp differences that are less than a second apart 157 if(options.backend == Backend.ninja) { 158 Thread.sleep(1.seconds); 159 } 160 161 auto file = File(buildPath(options.workingDir, fileName), "w"); 162 file.writeln(newContents); 163 } 164 165 // used to change files and cause a rebuild 166 void overwrite(in string fileName, in string newContents) { 167 import reggae.config; 168 overwrite(options, fileName, newContents); 169 } 170 171 172 string[] ninja(string[] args = []) { 173 return ["ninja", "-j1"] ~ args; 174 } 175 176 string[] make(string[] args = []) { 177 return ["make"] ~ args; 178 } 179 180 string[] tup(string[] args = []) { 181 return ["tup"] ~ args; 182 } 183 184 string[] binary(string path, string[] args = []) { 185 import std.path; 186 return [buildPath(path, "build"), "--norerun", "--single"] ~ args; 187 } 188 189 string[] buildCmd(in Options options, string[] args = []) { 190 return buildCmd(options.backend, options.workingDir, args); 191 } 192 193 string[] buildCmd(Backend backend, string path, string[] args = []) { 194 final switch(backend) { 195 case Backend.ninja: 196 return ninja(args); 197 case Backend.make: 198 return make(args); 199 case Backend.tup: 200 return tup(args); 201 case Backend.binary: 202 return binary(path, args); 203 case Backend.none: 204 return []; 205 } 206 } 207 208 // do a build in the integration test context 209 // this uses the build description to generate the build 210 // then runs the build command 211 void doTestBuildFor(string module_ = __MODULE__)(ref Options options, string[] args = []) { 212 prepareTestBuild!module_(options); 213 justDoTestBuild!module_(options, args); 214 } 215 216 void prepareTestBuild(string module_ = __MODULE__)(ref Options options) { 217 import std.file; 218 import std.string; 219 import std.path; 220 import std.algorithm: canFind; 221 import reggae.config; 222 223 mkdirRecurse(buildPath(options.workingDir, ".reggae")); 224 symlink(buildPath(testsPath, "dcompile"), buildPath(options.workingDir, ".reggae", "dcompile")); 225 226 // copy the project files over, that way the tests can modify them 227 immutable projectsPath = buildPath(origPath, "tests", "projects"); 228 immutable projectName = module_.split(".")[0]; 229 immutable projectPath = buildPath(projectsPath, projectName); 230 231 // change the directory of the project to be where the build dir is 232 options.projectPath = buildPath(origPath, (options.workingDir).relativePath(origPath)); 233 auto modulePath = buildPath(projectsPath, module_.split(".").join(dirSeparator)); 234 235 // copy all project files over to the build directory 236 if(module_.canFind("reggaefile")) { 237 copyProjectFiles(projectPath, options.workingDir); 238 options.projectPath = options.workingDir; 239 } 240 241 setOptions(options); 242 } 243 244 void justDoTestBuild(string module_ = __MODULE__)(in Options options, string[] args = []) { 245 import tests.utils; 246 247 auto cmdArgs = buildCmd(options, args); 248 doBuildFor!module_(options, cmdArgs); // generate build 249 if(options.backend != Backend.binary && options.backend != Backend.none) 250 cmdArgs.shouldExecuteOk(options.workingDir); 251 } 252 253 string[] buildCmdShouldRunOk(alias module_ = __MODULE__)(in Options options, 254 string[] args = [], 255 string file = __FILE__, 256 ulong line = __LINE__ ) { 257 import tests.utils; 258 auto cmdArgs = buildCmd(options, args); 259 260 string[] doTheBuild() { 261 doBuildFor!module_(options, cmdArgs); 262 return []; 263 } 264 265 // the binary backend in the tests isn't a separate executable, but make, ninja and tup are 266 return options.backend == Backend.binary 267 ? doTheBuild 268 : cmdArgs.shouldExecuteOk(options.workingDir, file, line); 269 } 270 271 // copy one of the test projects to a temporary test directory 272 void copyProjectFiles(in string projectPath, in string testPath) { 273 import std.file; 274 import std.path; 275 foreach(entry; dirEntries(projectPath, SpanMode.depth)) { 276 if(entry.isDir) continue; 277 auto tgtName = buildPath(testPath, entry.relativePath(projectPath)); 278 auto dir = dirName(tgtName); 279 if(!dir.exists) mkdirRecurse(dir); 280 copy(entry, buildPath(testPath, tgtName)); 281 } 282 } 283 284 // whether a file exists in the test sandbox 285 void shouldNotExist(string fileName, string file = __FILE__, size_t line = __LINE__) { 286 import reggae.config; 287 import std.file; 288 289 fileName = inPath(options, fileName); 290 if(fileName.exists) { 291 throw new UnitTestException(["File " ~ fileName ~ " was not expected to exist but does"]); 292 } 293 }