// 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.simple_vector; import ecs.std; enum int ASSERTED = 123; enum string OUT_FILE = "test_report.xml"; static jmp_buf gEnvBuffer; static 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 file_line; string name; //string classname; int time; bool passed; } struct TestSuite { string name; uint passed = 0; uint failed = 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); } struct TestRunner(Args...) { TestSuite[Args.length] suites; uint passed = 0; uint failed = 0; SimpleVector junit; void write(string txt) { junit.add(cast(ubyte[]) txt); } void write(uint num) { ubyte[20] buffer; int len; len = sprintf(cast(char*)buffer.ptr, "%u", num); junit.add(buffer[0..len]); } void generateJUnit() { write("\n\n"); write("\n"); foreach(ref TestSuite suite; suites) { write("\t\n"); foreach(ref Test test; suite.tests) { write("\t\t\n"); if(test.msg) { write("\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"); } write("\t\n"); } write(""); } void runTests() { foreach(i, module_; Args) { TestSuite* suite = &suites[i]; suite.name = module_.stringof; foreach (index, unittest_; __traits(getUnitTests, module_)) { enum attributes = __traits(getAttributes, unittest_); Test test; static if (attributes.length == 0)test.name = "None"; else test.name = attributes[0]; // Save calling environment for longjmp int jmp_ret = setjmp(gEnvBuffer); if (jmp_ret == ASSERTED) { passed = false; test.passed = false; test.file = copyString(gAssertInfo.file); test.file_line = gAssertInfo.line; test.msg = copyString(gAssertInfo.msg); } else { unittest_(); test.passed = true; } if(test.passed)suite.passed++; else suite.failed++; suite.tests ~= test; } passed += suite.passed; failed += suite.failed; } } 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\n", suite.name.ptr, suite.passed, suite.passed + suite.failed); 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.\n", passed, passed+failed); } } int main() { import tests.vector; import tests.id_manager; import tests.basic; TestRunner!(tests.runner, tests.id_manager, tests.vector, tests.basic) runner; runner.runTests(); runner.writeFile(); runner.writeOutput(); if(!runner.failed)return 0; else return 1; }