bubel-ecs/tests/runner.d
Mergul 37d15f97d6 -fixed some meson issues on windows (becs and tests compilation works. Demos not yet)
-now tests compile proper files
-added setjmp bindings for Windows. It doesn't work on LDC build (singal API could works)
2021-03-06 22:00:08 +01:00

481 lines
No EOL
13 KiB
D

// Example usage: dub -c unittest-runner -b unittest
module tests.runner;
import core.stdc.stdio;
import core.stdc.string;
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";
version (D_BetterC)
{
version(Posix)
{
import core.sys.posix.setjmp;
}
else version(Windows)
{
version(X86)
alias jmp_buf = ubyte[64];
else version(X86_64)
alias jmp_buf = ubyte[256];
else version(IA64)
alias jmp_buf = ubyte[512];
extern (C) {
int _setjmp(ref jmp_buf);
void longjmp(ref jmp_buf, int);
}
alias setjmp = _setjmp;
}
static jmp_buf gEnvBuffer;
static AssertInfo gAssertInfo;
extern (C) void __assert(const char* msg, const char* file, int line)
{
gAssertInfo = AssertInfo(msg, file, line);
longjmp(gEnvBuffer, ASSERTED);
}
}
else version = notBetterC;
struct AssertInfo
{
const(char)* msg;
const(char)* file;
int line;
}
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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
write("<testsuites tests=\"");
write(passed + failed);
write("\" failures=\"");
write(failed);
write("\">\n");
foreach (ref TestSuite suite; suites)
{
write("\t<testsuite name=\"");
write(suite.name);
write("\" tests=\"");
write(suite.passed + suite.failed);
write("\" failures=\"");
write(suite.failed);
write("\" skipped=\"");
write(suite.skipped);
write("\">\n");
foreach (ref Test test; suite.tests)
{
write("\t\t<testcase name=\"");
write(test.name);
write("\" classname=\"");
write(suite.name);
write("\" time=\"");
write(cast(double)test.time*0.000001);
write("\">");
if (test.msg)
{
write("\n\t\t\t<failure type=\"Fail\" message=\"");
write(test.msg[0 .. $ - 1]);
write("\">");
write("Assert! File: ");
write(test.file[0 .. $ - 1]);
write(":");
write(test.file_line);
write(" Message: ");
write(test.msg[0 .. $ - 1]);
write("</failure>\n");
write("\t\t</testcase>\n");
}
else write("</testcase>\n");
}
write("\t</testsuite>\n");
}
write("</testsuites>");
}
@(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, RangeError;
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);
}
catch(RangeError 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 .. $]);
}
}
static import tests.id_manager;
static import tests.vector;
static import tests.basic;
static import tests.perf;
static import tests.access_perf;
static import tests.bugs;
static import tests.map;
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;
}
}
}