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 }