// 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 ecs.vector; import ecs.std; unittest { assert(0, "AAAAAAAA"); } enum int ASSERTED = 123; enum string OUT_FILE = "test_report.xml"; __gshared jmp_buf gEnvBuffer; __gshared TestSuite[2] gSuites; __gshared AssertInfo gAssertInfo; 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 fileLine; string name; int time; bool passed; bool executed; } struct TestSuite { string name; Vector!Test tests; } void writeToFile(string data) { auto file = fopen(OUT_FILE, "a"); fwrite(data.ptr, 1, data.length, file); fclose(file); } void writeToFile(int num) { auto file = fopen(OUT_FILE, "a"); fprintf(file, "%d", num); fclose(file); } void writeResulStart(int num) { // Clear file auto file = fopen(OUT_FILE, "w"); fclose(file); writeToFile("\n"); } void writeResulEnd() { writeToFile("\n"); } void writeResult(ref TestSuite suite, ref Test result) { writeToFile(" \n"); } else { writeToFile(">\n"); writeToFile(" "); if (result.file.length != 0) { writeToFile("Assert! File: "); writeToFile(result.file[0 .. $ - 1]); writeToFile(":"); writeToFile(result.fileLine); writeToFile(" Message: "); writeToFile(result.msg[0 .. $ - 1]); } else { writeToFile("No Message"); } writeToFile("\n"); writeToFile(" \n"); } } void writeTests(TestSuite[] suites) { writeResulStart(cast(int) suites.length); foreach (ref TestSuite suite; suites) { foreach (ref Test test; suite.tests) { writeResult(suite, test); } } writeResulEnd(); } string copyString(const char* str) { auto length = strlen(str); char[] arr = cast(char[]) str[0 .. length + 1]; return cast(string) Mallocator.makeArray(arr); } void runTestSuite(alias testModule)(ref TestSuite suite) { suite.name = testModule.stringof; foreach (index, testDelegate; __traits(getUnitTests, testModule)) { suite.tests[index].executed = true; writeTests(gSuites); // Save executed info // Save calling environment for longjmp int jmpRet = setjmp(gEnvBuffer); if (jmpRet == ASSERTED) { suite.tests[index].passed = false; suite.tests[index].file = copyString(gAssertInfo.file); suite.tests[index].fileLine = gAssertInfo.line; suite.tests[index].msg = copyString(gAssertInfo.msg); } else { testDelegate(); suite.tests[index].passed = true; } } } void addTestSuite(alias testModule)(ref TestSuite suite) { suite.name = testModule.stringof; foreach (index, testDelegate; __traits(getUnitTests, testModule)) { enum attributes = __traits(getAttributes, testDelegate); static if (attributes.length == 0) { enum string testName = "No name"; } else { enum string testName = attributes[0]; } Test test; test.name = testName; suite.tests ~= test; } } void main() { addTestSuite!(tests.runner)(gSuites[0]); addTestSuite!(ecs.id_manager)(gSuites[1]); writeTests(gSuites); // Save results in case that there are no tests runTestSuite!(tests.runner)(gSuites[0]); runTestSuite!(ecs.id_manager)(gSuites[1]); writeTests(gSuites); // Save result of last test }