1 module unit_threaded.factory;
2 
3 import unit_threaded.testcase;
4 import unit_threaded.list;
5 import unit_threaded.asserts;
6 import unit_threaded.check;
7 
8 import std.stdio;
9 import std.traits;
10 import std.typetuple;
11 import std.exception;
12 import std.algorithm;
13 import std.string;
14 import core.runtime;
15 
16 /**
17  * Replace the D runtime's normal unittest block tester with our own
18  */
19 static this() {
20     Runtime.moduleUnitTester = &moduleUnitTester;
21 }
22 
23 /**
24  * Creates tests cases from the given modules.
25  * If testsToRun is empty, it means run all tests.
26  */
27 TestCase[] createTests(MODULES...)(in string[] testsToRun = []) if(MODULES.length > 0) {
28     TestCase[] tests;
29     foreach(data; getTestClassesAndFunctions!MODULES()) {
30         if(!isWantedTest(data, testsToRun)) continue;
31         auto test = createTestCase(data);
32         if(test !is null) tests ~= test; //can be null if abtract base class
33     }
34 
35     foreach(test; builtinTests) { //builtInTests defined below
36         if(isWantedTest(test.getPath(), testsToRun)) tests ~= test;
37     }
38 
39     return tests;
40 }
41 
42 private TestCase createTestCase(TestData data) {
43     auto testCase = data.test is null ?
44         cast(TestCase) Object.factory(data.name):
45         new FunctionTestCase(data);
46 
47     if(data.test !is null) {
48         assert(testCase !is null, "Could not create FunctionTestCase object for function " ~ data.name);
49     }
50 
51     return testCase;
52 }
53 
54 private bool isWantedTest(in TestData data, in string[] testsToRun) {
55     if(!testsToRun.length) return !data.hidden; //"all tests (except hidden)"
56     return isWantedTest(data.name, testsToRun);
57 }
58 
59 private bool isWantedTest(in string name, in string[] testsToRun) {
60     if(!testsToRun.length) return true;
61     foreach(testToRun; testsToRun) {
62         if(startsWith(name, testToRun)) return true; //even if hidden
63     }
64     return false;
65 }
66 
67 private class FunctionTestCase: TestCase {
68     this(immutable TestData data) pure nothrow {
69         _name = data.name;
70         _func = data.test;
71     }
72 
73     override void test() {
74         _func();
75     }
76 
77     override string getPath() const pure nothrow {
78         return _name;
79     }
80 
81     private string _name;
82     private TestFunction _func;
83 }
84 
85 package auto getTestClassesAndFunctions(MODULES...)() {
86     return getAllTests!(q{getTestClassNames}, MODULES)() ~
87            getAllTests!(q{getTestFunctions}, MODULES)();
88 }
89 
90 private auto getAllTests(string expr, MODULES...)() pure nothrow {
91     //tests is whatever type expr returns
92     ReturnType!(mixin(expr ~ q{!(MODULES[0])})) tests;
93     foreach(mod; TypeTuple!MODULES) {
94         tests ~= mixin(expr ~ q{!mod()});
95     }
96     return assumeUnique(tests);
97 }
98 
99 
100 private TestCase[] builtinTests; //built-in unittest blocks
101 
102 private class BuiltinTestCase: FunctionTestCase {
103     this(immutable TestData data) pure nothrow {
104         super(data);
105     }
106 
107     override void test() {
108         try {
109             super.test();
110         } catch(Throwable t) {
111             utFail(t.msg, t.file, t.line);
112         }
113     }
114 }
115 
116 private bool moduleUnitTester() {
117     foreach(mod; ModuleInfo) {
118         if(mod && mod.unitTest) {
119             if(startsWith(mod.name, "unit_threaded.")) {
120                 mod.unitTest()();
121             } else {
122                 enum hidden = false;
123                 builtinTests ~=
124                     new BuiltinTestCase(TestData(mod.name ~ ".unittest", hidden, mod.unitTest));
125             }
126         }
127     }
128 
129     return true;
130 }
131 
132 
133 unittest {
134     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"]));
135     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests."]));
136     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"]));
137     assert(isWantedTest(TestData("pass_tests.testEqual"), []));
138     assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"]));
139     assert(!isWantedTest("example.tests.pass.normal.unittest",
140                          ["example.tests.pass.io.TestFoo"]));
141     assert(isWantedTest("example.tests.pass.normal.unittest", []));
142 }