// Example usage: dub -c unittest-runner -b unittest module tests.runner; import core.stdc.stdio; import core.stdc.string; import core.sys.posix.setjmp; import bubel.ecs.vector; import bubel.ecs.simple_vector; import bubel.ecs.std; import std.traits; import tests.time; version (LDC) { import ldc.attributes; } else { enum optStrategy = 0; } enum int ASSERTED = 123; enum string OUT_FILE = "test_report.xml"; static jmp_buf gEnvBuffer; static AssertInfo gAssertInfo; version (D_BetterC) { } else version = notBetterC; struct AssertInfo { const(char)* msg; const(char)* file; int line; } extern (C) void __assert(const char* msg, const char* file, int line) { gAssertInfo = AssertInfo(msg, file, line); longjmp(gEnvBuffer, ASSERTED); } struct Test { string file; string msg; int file_line; string name; //string classname; int time; bool passed; } struct TestSuite { string name; uint passed = 0; uint failed = 0; uint skipped = 0; Vector!Test tests; } string copyString(const char* str) { auto length = strlen(str); char[] arr = cast(char[]) str[0 .. length + 1]; return cast(string) Mallocator.makeArray(arr); } string copyString(string str) { return cast(string) Mallocator.makeArray((cast(char*)str)[0 .. str.length + 1]); } struct TestRunner(Args...) { void generateJUnit() { write("\n\n"); write("\n"); foreach (ref TestSuite suite; suites) { write("\t\n"); foreach (ref Test test; suite.tests) { write("\t\t"); if (test.msg) { write("\n\t\t\t"); write("Assert! File: "); write(test.file[0 .. $ - 1]); write(":"); write(test.file_line); write(" Message: "); write(test.msg[0 .. $ - 1]); write("\n"); write("\t\t\n"); } else write("\n"); } write("\t\n"); } write(""); } @(optStrategy,"none") void runTests(string[] include = null, string[] exclude = null) { foreach (i, module_; Args) { TestSuite* suite = &suites[i]; suite.name = module_.stringof; void function() before; void function() after; foreach (index, unittest_; __traits(getUnitTests, module_)) { enum attributes = __traits(getAttributes, unittest_); static if (attributes.length != 0) { foreach(attr_id, attr; attributes) { static if(isFunctionPointer!(attr)){} else static if(attr == "_tr_before") { static assert(attr_id+1 < attributes.length); enum attr2 = attributes[attr_id + 1]; //static assert(__traits(hasMember, module_, attr2)); //alias func = __traits(getMember, module_, attr2); //attr2(); before = attr2; //static assert(is(typeof(__traits(getMember, module_, attr2)) == void function())); } else { if (include.length > 0) { bool matched = false; foreach (str; include) { if (match(str, attr)) { matched = true; break; } } foreach (str; exclude) { if (match(str, attr)) { matched = false; break; } } if (!matched) { suite.skipped++; continue; } } } } } else if (include.length > 0) { suite.skipped++; continue; } Test test; static if (attributes.length == 0) test.name = "None"; else test.name = attributes[0]; static if (__traits(hasMember, module_, "beforeEveryTest") && __traits(compiles, module_.beforeEveryTest())) module_.beforeEveryTest(); if(before)before(); version(D_BetterC) { // Save calling environment for longjmp int jmp_ret = setjmp(gEnvBuffer); if (jmp_ret == ASSERTED) { test.passed = false; test.file = copyString(gAssertInfo.file); test.file_line = gAssertInfo.line; test.msg = copyString(gAssertInfo.msg); } else { long time = Time.getUSecTime(); unittest_(); test.passed = true; test.time = cast(int)(Time.getUSecTime() - time); } } else { import core.exception : AssertError; try { unittest_(); test.passed = true; } catch(AssertError error) { test.passed = false; test.file = copyString(error.file); test.file_line = cast(int)error.line; test.msg = copyString(error.msg); } } if (test.passed) suite.passed++; else suite.failed++; suite.tests ~= test; static if (__traits(hasMember, module_, "afterEveryTest") && __traits(compiles, module_.beforeEveryTest())) module_.afterEveryTest(); } passed += suite.passed; failed += suite.failed; skipped += suite.skipped; } } void writeFile() { if (junit.length == 0) generateJUnit(); auto file = fopen(OUT_FILE, "w"); fwrite(junit.data.ptr, junit.length, 1, file); fclose(file); } void writeOutput() { foreach (ref TestSuite suite; suites) { printf("Suite: \"%s\" Passed: %u/%u Skipped: %u\n", suite.name.ptr, suite.passed, suite.passed + suite.failed, suite.skipped); foreach (ref Test test; suite.tests) { if (!test.passed) { printf("\tTest: \"%s\" Failed! Line: %u Message: %s\n", test.name.ptr, test.file_line, test.msg.ptr); } } } printf("Passed %u/%u tests. Skipped: %u.\n", passed, passed + failed, skipped); } bool match(string a, string b) { uint i = 0; foreach (char c; b) { if (i > a.length) return false; if (a[i] == c) i++; else { if (a[i] == '*') { if (i + 1 >= a.length) return true; else if (a[i + 1] == c) i += 2; } else return false; } } return i == a.length; } void write(string txt) { junit.add(cast(ubyte[]) txt); } void write(double num) { ubyte[40] buffer; int len; len = sprintf(cast(char*) buffer.ptr, "%2.8lf", num); junit.add(buffer[0 .. len]); } void write(uint num) { ubyte[20] buffer; int len; len = sprintf(cast(char*) buffer.ptr, "%u", num); junit.add(buffer[0 .. len]); } TestSuite[Args.length] suites; uint passed = 0; uint failed = 0; uint skipped = 0; SimpleVector junit; } version (notBetterC) { extern (C) int rt_init(); extern (C) int rt_term(); version (D_Coverage) extern (C) void dmd_coverDestPath(string path); } enum before = "_tr_before"; void extractStrings(ref Vector!string container, string str) { uint s = 0; foreach (j, char c; str) { if (c == ',') { if (s < j) { container.add(str[s .. j]); } s = cast(uint) j + 1; } } if (s < str.length) { container.add(str[s .. $]); } } extern (C) int main(int argc, char** args) { Vector!string include; Vector!string exclude; version (notBetterC) { rt_init(); version (D_Coverage) dmd_coverDestPath("reports"); } for (int i = 1; i < argc; i++) { string arg = cast(string) args[i][0 .. strlen(args[i])]; if (arg.length < 2) continue; else if (arg == "-i") { if (i + 1 >= argc) break; i++; arg = cast(string) args[i][0 .. strlen(args[i])]; extractStrings(include, arg); } else if (arg == "-e") { if (i + 1 >= argc) break; i++; arg = cast(string) args[i][0 .. strlen(args[i])]; extractStrings(exclude, arg); } else if (arg.length < 10) continue; else if (arg[0 .. 10] == "--include=") { extractStrings(include, arg[10 .. $]); } else if (arg[0 .. 10] == "--exclude=") { extractStrings(exclude, arg[10 .. $]); } } TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf, tests.bugs, tests.map) runner; runner.runTests(include[], exclude[]); runner.writeFile(); runner.writeOutput(); version (notBetterC) rt_term(); if (!runner.failed) return 0; else return 1; } version (D_BetterC) { version(LDC) { extern (C) __gshared int _d_eh_personality(int, int, size_t, void*, void*) { return 0; } } }