1 module libd.testing.runner; 2 3 import libd.datastructures.smartptr; 4 5 @nogc nothrow: 6 7 private alias TestCaseFunc = void function(); 8 __gshared bool g_testRunnerRunning; 9 10 struct TestCase 11 { 12 string name; 13 TestCaseFunc func; 14 } 15 16 void testGatherCases(Modules...)(ref Array!TestCase cases) 17 { 18 static foreach(i; 0..Modules.length) 19 appendCases!(Modules[i])(cases); 20 } 21 22 private void appendCases(alias Aggregate)(ref Array!TestCase cases) 23 { 24 static if(__traits(compiles, __traits(getUnitTests, Aggregate))) 25 foreach(test; __traits(getUnitTests, Aggregate)) 26 {{ 27 string name = __traits(identifier, test); 28 29 static foreach(attrib; __traits(getAttributes, test)) 30 { 31 static if(is(typeof(attrib) == string)) 32 name = attrib; 33 } 34 35 cases.put(TestCase(name, cast(TestCaseFunc)&test)); // We compile under -betterC, @nogc nothrow is a complete guarentee because we also killed DRuntime 36 }} 37 38 static if(!__traits(isModule, Aggregate)) 39 static if(__traits(compiles, __traits(allMembers, Aggregate))) 40 static foreach(memberName; __traits(allMembers, Aggregate)) 41 {{ 42 alias Member = __traits(getMember, Aggregate, memberName); 43 static if(__traits(compiles, appendCases!Member(cases))) 44 appendCases!Member(cases); 45 }} 46 } 47 48 void testRunner(const bcstring[] args, ref const Array!TestCase cases) 49 { 50 import libd.console, libd.async, libd.algorithm; 51 g_testRunnerRunning = true; 52 scope(exit) g_testRunnerRunning = false; 53 54 enum Result 55 { 56 FAILSAFE, 57 success, 58 failure 59 } 60 61 static struct TestResult 62 { 63 TestCase test; 64 Result result; 65 BcError error; 66 } 67 68 Task testTask; 69 Array!TestResult results; 70 foreach(test; cases) 71 { 72 consoleWritefln("{0}{1}", "Running: ".ansi.fg(Ansi4BitColour.magenta), test.name); 73 // `assert` has special behaviour when we set g_testRunnerRunning. 74 // It expects unittests to be ran inside of a task, so it can then kill the task off and 75 // return an error, instead of killing the program off completely. 76 taskRun(testTask, (){ 77 taskAccessContext!(const(TestCase)*, (scope ref test) 78 { 79 test.func(); 80 }); 81 }, &test); 82 83 if(testTask.hasError) 84 results.put(TestResult(test, Result.failure, testTask.error)); 85 else 86 results.put(TestResult(test, Result.success)); 87 } 88 89 consoleWriteln("\n\nThe following tests were successful:".ansi.fg(Ansi4BitColour.green)); 90 foreach(pass; results.range.where!(test => test.result == Result.success)) 91 consoleWriteln('\t', pass.test.name.ansi.fg(Ansi4BitColour.green)); 92 93 consoleWriteln("\n\nThe following tests failed:".ansi.fg(Ansi4BitColour.red)); 94 foreach(fail; results.range.where!(test => test.result == Result.failure)) 95 { 96 consoleWriteln('\t', fail.test.name.ansi.fg(Ansi4BitColour.red)); 97 displayError(fail.error); 98 } 99 }