1 // Example usage: dub -c unittest-runner -b unittest 2 module tests.runner; 3 4 import core.stdc.stdio; 5 import core.stdc.string; 6 7 import bubel.ecs.vector; 8 import bubel.ecs.simple_vector; 9 import bubel.ecs.std; 10 11 import std.traits; 12 13 import tests.time; 14 15 version (LDC) 16 { 17 import ldc.attributes; 18 } 19 else 20 { 21 enum optStrategy = 0; 22 } 23 24 enum int ASSERTED = 123; 25 enum string OUT_FILE = "test_report.xml"; 26 27 version (D_BetterC) 28 { 29 version(Posix) 30 { 31 import core.sys.posix.setjmp; 32 } 33 else version(Windows) 34 { 35 version(X86) 36 alias jmp_buf = ubyte[64]; 37 else version(X86_64) 38 alias jmp_buf = ubyte[256]; 39 else version(IA64) 40 alias jmp_buf = ubyte[512]; 41 42 extern (C) { 43 int _setjmp(ref jmp_buf); 44 void longjmp(ref jmp_buf, int); 45 } 46 alias setjmp = _setjmp; 47 } 48 49 static jmp_buf gEnvBuffer; 50 static AssertInfo gAssertInfo; 51 52 extern (C) void __assert(const char* msg, const char* file, int line) 53 { 54 gAssertInfo = AssertInfo(msg, file, line); 55 longjmp(gEnvBuffer, ASSERTED); 56 } 57 } 58 else version = notBetterC; 59 60 struct AssertInfo 61 { 62 const(char)* msg; 63 const(char)* file; 64 int line; 65 } 66 67 struct Test 68 { 69 string file; 70 string msg; 71 int file_line; 72 string name; 73 //string classname; 74 int time; 75 bool passed; 76 } 77 78 struct TestSuite 79 { 80 string name; 81 uint passed = 0; 82 uint failed = 0; 83 uint skipped = 0; 84 Vector!Test tests; 85 } 86 87 string copyString(const char* str) 88 { 89 auto length = strlen(str); 90 char[] arr = cast(char[]) str[0 .. length + 1]; 91 return cast(string) Mallocator.makeArray(arr); 92 } 93 94 string copyString(string str) 95 { 96 return cast(string) Mallocator.makeArray((cast(char*)str)[0 .. str.length + 1]); 97 } 98 99 struct TestRunner(Args...) 100 { 101 void generateJUnit() 102 { 103 write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n"); 104 105 write("<testsuites tests=\""); 106 write(passed + failed); 107 write("\" failures=\""); 108 write(failed); 109 write("\">\n"); 110 111 foreach (ref TestSuite suite; suites) 112 { 113 write("\t<testsuite name=\""); 114 write(suite.name); 115 write("\" tests=\""); 116 write(suite.passed + suite.failed); 117 write("\" failures=\""); 118 write(suite.failed); 119 write("\" skipped=\""); 120 write(suite.skipped); 121 write("\">\n"); 122 123 foreach (ref Test test; suite.tests) 124 { 125 write("\t\t<testcase name=\""); 126 write(test.name); 127 write("\" classname=\""); 128 write(suite.name); 129 write("\" time=\""); 130 write(cast(double)test.time*0.000001); 131 write("\">"); 132 if (test.msg) 133 { 134 write("\n\t\t\t<failure type=\"Fail\" message=\""); 135 write(test.msg[0 .. $ - 1]); 136 write("\">"); 137 write("Assert! File: "); 138 write(test.file[0 .. $ - 1]); 139 write(":"); 140 write(test.file_line); 141 write(" Message: "); 142 write(test.msg[0 .. $ - 1]); 143 write("</failure>\n"); 144 write("\t\t</testcase>\n"); 145 } 146 else write("</testcase>\n"); 147 148 } 149 150 write("\t</testsuite>\n"); 151 } 152 153 write("</testsuites>"); 154 } 155 156 @(optStrategy,"none") 157 void runTests(string[] include = null, string[] exclude = null) 158 { 159 foreach (i, module_; Args) 160 { 161 TestSuite* suite = &suites[i]; 162 suite.name = module_.stringof; 163 164 void function() before; 165 void function() after; 166 167 foreach (index, unittest_; __traits(getUnitTests, module_)) 168 { 169 enum attributes = __traits(getAttributes, unittest_); 170 171 static if (attributes.length != 0) 172 { 173 foreach(attr_id, attr; attributes) 174 { 175 static if(isFunctionPointer!(attr)){} 176 else static if(attr == "_tr_before") 177 { 178 static assert(attr_id+1 < attributes.length); 179 enum attr2 = attributes[attr_id + 1]; 180 //static assert(__traits(hasMember, module_, attr2)); 181 //alias func = __traits(getMember, module_, attr2); 182 //attr2(); 183 before = attr2; 184 //static assert(is(typeof(__traits(getMember, module_, attr2)) == void function())); 185 } 186 else 187 { 188 if (include.length > 0) 189 { 190 bool matched = false; 191 foreach (str; include) 192 { 193 if (match(str, attr)) 194 { 195 matched = true; 196 break; 197 } 198 } 199 200 foreach (str; exclude) 201 { 202 if (match(str, attr)) 203 { 204 matched = false; 205 break; 206 } 207 } 208 209 if (!matched) 210 { 211 suite.skipped++; 212 continue; 213 } 214 } 215 } 216 } 217 } 218 else if (include.length > 0) 219 { 220 suite.skipped++; 221 continue; 222 } 223 224 Test test; 225 226 static if (attributes.length == 0) 227 test.name = "None"; 228 else 229 test.name = attributes[0]; 230 231 static if (__traits(hasMember, module_, "beforeEveryTest") && __traits(compiles, module_.beforeEveryTest())) 232 module_.beforeEveryTest(); 233 if(before)before(); 234 235 version(D_BetterC) 236 { 237 // Save calling environment for longjmp 238 int jmp_ret = setjmp(gEnvBuffer); 239 240 if (jmp_ret == ASSERTED) 241 { 242 test.passed = false; 243 test.file = copyString(gAssertInfo.file); 244 test.file_line = gAssertInfo.line; 245 test.msg = copyString(gAssertInfo.msg); 246 } 247 else 248 { 249 long time = Time.getUSecTime(); 250 unittest_(); 251 test.passed = true; 252 test.time = cast(int)(Time.getUSecTime() - time); 253 } 254 } 255 else 256 { 257 import core.exception : AssertError, RangeError; 258 try 259 { 260 unittest_(); 261 test.passed = true; 262 } 263 catch(AssertError error) 264 { 265 test.passed = false; 266 test.file = copyString(error.file); 267 test.file_line = cast(int)error.line; 268 test.msg = copyString(error.msg); 269 } 270 catch(RangeError error) 271 { 272 test.passed = false; 273 test.file = copyString(error.file); 274 test.file_line = cast(int)error.line; 275 test.msg = copyString(error.msg); 276 } 277 } 278 279 if (test.passed) 280 suite.passed++; 281 else 282 suite.failed++; 283 suite.tests ~= test; 284 static if (__traits(hasMember, module_, "afterEveryTest") && __traits(compiles, module_.beforeEveryTest())) 285 module_.afterEveryTest(); 286 } 287 passed += suite.passed; 288 failed += suite.failed; 289 skipped += suite.skipped; 290 } 291 } 292 293 void writeFile() 294 { 295 if (junit.length == 0) 296 generateJUnit(); 297 auto file = fopen(OUT_FILE, "w"); 298 fwrite(junit.data.ptr, junit.length, 1, file); 299 fclose(file); 300 } 301 302 void writeOutput() 303 { 304 foreach (ref TestSuite suite; suites) 305 { 306 printf("Suite: \"%s\" Passed: %u/%u Skipped: %u\n", suite.name.ptr, 307 suite.passed, suite.passed + suite.failed, suite.skipped); 308 foreach (ref Test test; suite.tests) 309 { 310 if (!test.passed) 311 { 312 printf("\tTest: \"%s\" Failed! Line: %u Message: %s\n", 313 test.name.ptr, test.file_line, test.msg.ptr); 314 } 315 } 316 } 317 printf("Passed %u/%u tests. Skipped: %u.\n", passed, passed + failed, skipped); 318 } 319 320 bool match(string a, string b) 321 { 322 uint i = 0; 323 foreach (char c; b) 324 { 325 if (i > a.length) 326 return false; 327 if (a[i] == c) 328 i++; 329 else 330 { 331 if (a[i] == '*') 332 { 333 if (i + 1 >= a.length) 334 return true; 335 else if (a[i + 1] == c) 336 i += 2; 337 } 338 else 339 return false; 340 } 341 } 342 return i == a.length; 343 } 344 345 void write(string txt) 346 { 347 junit.add(cast(ubyte[]) txt); 348 } 349 350 void write(double num) 351 { 352 ubyte[40] buffer; 353 int len; 354 len = sprintf(cast(char*) buffer.ptr, "%2.8lf", num); 355 junit.add(buffer[0 .. len]); 356 } 357 358 void write(uint num) 359 { 360 ubyte[20] buffer; 361 int len; 362 len = sprintf(cast(char*) buffer.ptr, "%u", num); 363 junit.add(buffer[0 .. len]); 364 } 365 366 TestSuite[Args.length] suites; 367 uint passed = 0; 368 uint failed = 0; 369 uint skipped = 0; 370 371 SimpleVector junit; 372 } 373 374 version (notBetterC) 375 { 376 extern (C) int rt_init(); 377 extern (C) int rt_term(); 378 version (D_Coverage) extern (C) void dmd_coverDestPath(string path); 379 } 380 381 enum before = "_tr_before"; 382 383 void extractStrings(ref Vector!string container, string str) 384 { 385 uint s = 0; 386 foreach (j, char c; str) 387 { 388 if (c == ',') 389 { 390 if (s < j) 391 { 392 container.add(str[s .. j]); 393 } 394 s = cast(uint) j + 1; 395 } 396 } 397 if (s < str.length) 398 { 399 container.add(str[s .. $]); 400 } 401 } 402 403 extern (C) int main(int argc, char** args) 404 { 405 Vector!string include; 406 Vector!string exclude; 407 408 version (notBetterC) 409 { 410 rt_init(); 411 version (D_Coverage) 412 dmd_coverDestPath("reports"); 413 } 414 415 for (int i = 1; i < argc; i++) 416 { 417 string arg = cast(string) args[i][0 .. strlen(args[i])]; 418 if (arg.length < 2) 419 continue; 420 else if (arg == "-i") 421 { 422 if (i + 1 >= argc) 423 break; 424 i++; 425 arg = cast(string) args[i][0 .. strlen(args[i])]; 426 extractStrings(include, arg); 427 } 428 else if (arg == "-e") 429 { 430 if (i + 1 >= argc) 431 break; 432 i++; 433 arg = cast(string) args[i][0 .. strlen(args[i])]; 434 extractStrings(exclude, arg); 435 } 436 else if (arg.length < 10) 437 continue; 438 else if (arg[0 .. 10] == "--include=") 439 { 440 extractStrings(include, arg[10 .. $]); 441 } 442 else if (arg[0 .. 10] == "--exclude=") 443 { 444 extractStrings(exclude, arg[10 .. $]); 445 } 446 } 447 448 static import tests.id_manager; 449 static import tests.vector; 450 static import tests.basic; 451 static import tests.perf; 452 static import tests.access_perf; 453 static import tests.bugs; 454 static import tests.map; 455 TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf, tests.bugs, tests.map) runner; 456 457 runner.runTests(include[], exclude[]); 458 459 runner.writeFile(); 460 461 runner.writeOutput(); 462 463 version (notBetterC) 464 rt_term(); 465 466 if (!runner.failed) 467 return 0; 468 else 469 return 1; 470 } 471 472 version (D_BetterC) 473 { 474 version(LDC) 475 { 476 extern (C) __gshared int _d_eh_personality(int, int, size_t, void*, void*) 477 { 478 return 0; 479 } 480 } 481 }