diff --git a/dub.json b/dub.json
index 87b9b7b..b7de52f 100755
--- a/dub.json
+++ b/dub.json
@@ -86,6 +86,22 @@
"lflags-gdc": [
"-lpthread"
]
+ },
+
+ {
+ "name": "unittest-runner",
+ "targetType" : "executable",
+ "dflags": [
+ "-betterC"
+ ],
+ "sourceFiles":[
+ "tests/runner.d"
+ ],
+ "excludedSourceFiles":[
+ "source\/win_dll.d"
+ ]
}
+
+
]
}
\ No newline at end of file
diff --git a/tests/runner.d b/tests/runner.d
new file mode 100644
index 0000000..a31b1a6
--- /dev/null
+++ b/tests/runner.d
@@ -0,0 +1,206 @@
+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
+}