1 /**
2  * Experimental custom unittest runner.
3  *
4  * It's not part of iz but it's used to test the library.
5  */
6 module iz.testing;
7 
8 import
9     std.traits, std.typecons;
10 
11 alias TestProto = void function();
12 alias TestBattery = TestProto[];
13 alias OwnedTestBattery = Tuple!(string, TestBattery);
14 
15 enum UnitTest;
16 
17 /**
18  * Returns the unittests located in parent.
19  *
20  * Params:
21  *      parent = A module, a class, a struct, ... that contains the tests.
22  *      filter = A template that accepts a TestProto and that evaluates to a bool.
23  *
24  * Returns:
25  *      A tuple made of a string that represents the test owner and an array of
26  *      void function() that represents the tests located in the parent.
27  */
28 OwnedTestBattery collectTest(alias parent, alias filter = null)()
29 {
30     TestBattery result;
31     foreach(test; __traits(getUnitTests, parent))
32     {
33         static if (is(typeof(filter!test)))
34         {
35             static if (filter!test)
36                 result ~= &test;
37         }
38         else result ~= &test;
39     }
40     return tuple(parent.stringof, result);
41 }
42 
43 /**
44  * Example of a template that can be used as filter in collectTest.
45  */
46 template selectTestWithUda(alias T)
47 {
48     enum selectTestWithUda = hasUDA!(T, UnitTest);
49 }
50 
51 /**
52  * Runs a test battery, printing the progress to stdout.
53  *
54  * Params:
55  *      t = an OwnedTestBattery.
56  *
57  * Returns:
58  *      A boolean indicating if the tests are all passed.
59  */
60 bool runTestBattery(T, bool stopOnFailure = true)(T t)
61 {
62     import std.stdio: writeln, stderr, stdout;
63     import core.exception: AssertError;
64 
65     bool result = true;
66     static if (is(T == OwnedTestBattery))
67     {
68         writeln("running tests for: ", t[0]);
69         foreach(i, test; t[1])
70         {
71             try
72             {
73                 test();
74             }
75             catch (AssertError err)
76             {
77                 result = false;
78                 writeln("test ", i, ": failed");
79                 writeln(err.file, "(", err.line, "):", err.msg);
80                 static if (stopOnFailure)
81                 {
82                     throw err;
83                 }
84                 else 
85                     continue;
86             }
87             writeln("test ", i, ": passed");
88             stdout.flush;
89         }
90     }
91     else static assert(0, T.stringof ~ " not handled by " ~ __FUNCTION__);
92     return result;
93 }
94 
95 /**
96  * Generates a string that represents the code to run the tests in a list of module.
97  *
98  * Params:
99  *      Modules = A string that represents the list of the modules to test.
100  */
101 string libraryTestCode(string Modules, bool stopOnFailure = true)()
102 {
103     return
104     "
105         static this()
106         {
107             import core.runtime;
108             core.runtime.Runtime.moduleUnitTester = &testModules!("
109                 ~ stopOnFailure.stringof ~ "," ~ Modules ~ ");
110         }
111 
112         void main() {}
113     ";
114 }
115 
116 /// libraryTestCode() routine
117 bool testModules(bool stopOnFailure, Modules...)()
118 {
119     import std.stdio: writeln;
120 
121     bool result = true;
122     foreach(m; Modules)
123     {
124         auto tests = collectTest!(m)();
125         if (!tests.runTestBattery)
126         {
127             result = false;
128             static if (stopOnFailure)
129                 break;            
130         }            
131     }
132     if (result)
133         writeln("All the tests passed");
134 
135     return result;
136 }
137