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.array;
14 import std.string;
15 import core.runtime;
16 
17 /**
18  * Replace the D runtime's normal unittest block tester with our own
19  */
20 shared static this() {
21     Runtime.moduleUnitTester = &moduleUnitTester;
22 }
23 
24 /**
25  * Creates tests cases from the given modules.
26  * If testsToRun is empty, it means run all tests.
27  */
28 auto createTests(MODULES...)(in string[] testsToRun = []) if(MODULES.length > 0) {
29     bool[TestCase] tests;
30     foreach(data; getAllTestCases!MODULES()) {
31         if(!isWantedTest(data, testsToRun)) continue;
32         auto test = createTestCase(data);
33         if(test !is null) tests[test] = true; //can be null if abtract base class
34     }
35 
36     return tests.keys.sort!((a, b) => a.getPath < b.getPath).array;
37 }
38 
39 
40 private TestCase createTestCase(TestData data) {
41     TestCase createImpl(TestData data) {
42         if(data.test is null) return cast(TestCase) Object.factory(data.name);
43         return data.builtin ? new BuiltinTestCase(data) : new FunctionTestCase(data);
44     }
45 
46     if(data.singleThreaded) {
47         static CompositeTestCase[string] composites;
48         const moduleName = getModuleName(data.name);
49         if(moduleName !in composites) composites[moduleName] = new CompositeTestCase;
50         composites[moduleName] ~= createImpl(data);
51         return composites[moduleName];
52     }
53 
54     if(data.shouldFail) {
55         return new ShouldFailTestCase(createImpl(data));
56     }
57 
58     auto testCase = createImpl(data);
59     if(data.test !is null) {
60         assert(testCase !is null, "Could not create TestCase object for test " ~ data.name);
61     }
62 
63     return testCase;
64 }
65 
66 private string getModuleName(in string name) {
67     return name.splitter(".").array[0 .. $ -1].reduce!((a, b) => a ~ "." ~ b);
68 }
69 
70 
71 private bool isWantedTest(in TestData data, in string[] testsToRun) {
72     if(!testsToRun.length) return !data.hidden; //all tests except the hidden ones
73     bool matchesExactly(in string t) { return t == data.name; }
74     bool matchesPackage(in string t) { //runs all tests in package if it matches
75         with(data) return !hidden && name.length > t.length &&
76                        name.startsWith(t) && name[t.length .. $].canFind(".");
77     }
78     return testsToRun.any!(t => matchesExactly(t) || matchesPackage(t));
79 }
80 
81 
82 package auto getAllTestCases(MODULES...)() {
83     auto getAllTestsWithFunc(string expr, MODULES...)() pure nothrow {
84         //tests is whatever type expr returns
85         ReturnType!(mixin(expr ~ q{!(MODULES[0])})) tests;
86         foreach(mod; TypeTuple!MODULES) {
87             tests ~= mixin(expr ~ q{!mod()}); //e.g. tests ~= getTestClasses!mod
88         }
89         return assumeUnique(tests);
90     }
91 
92     return getAllTestsWithFunc!(q{getTestClasses}, MODULES) ~
93            getAllTestsWithFunc!(q{getTestFunctions}, MODULES) ~
94            getAllTestsWithFunc!(q{getBuiltinTests}, MODULES);
95 }
96 
97 
98 private bool moduleUnitTester() {
99     //this is so unit-threaded's own tests run
100     foreach(mod; ModuleInfo) {
101         if(mod && mod.unitTest) {
102             if(startsWith(mod.name, "unit_threaded.")) {
103                 mod.unitTest()();
104             }
105         }
106     }
107 
108     return true;
109 }
110 
111 
112 unittest {
113     //existing, wanted
114     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"]));
115     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."]));
116     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"]));
117     assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"]));
118     assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"]));
119     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"]));
120     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"]));
121     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"]));
122     assert(isWantedTest(TestData("pass_tests.testEqual"), []));
123     assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"]));
124     assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"),
125                          ["example.tests.pass.io.TestFoo"]));
126     assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), []));
127     assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", true /*hidden*/), ["tests.pass"]));
128 }
129 
130 
131 
132 unittest {
133     import unit_threaded.asserts;
134     assertEqual(getModuleName("tests.fail.composite.Test1"), "tests.fail.composite");
135 }