1 module unit_threaded.factory;
2 
3 import unit_threaded.testcase;
4 import unit_threaded.reflection;
5 import unit_threaded.asserts;
6 import unit_threaded.should;
7 
8 import std.stdio;
9 import std.traits;
10 import std.typetuple;
11 import std.algorithm;
12 import std.array;
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 shared static this() {
20     Runtime.moduleUnitTester = &moduleUnitTester;
21 }
22 
23 private bool moduleUnitTester() {
24     //this is so unit-threaded's own tests run
25     foreach(module_; ModuleInfo) {
26         if(module_ && module_.unitTest) {
27             if(startsWith(module_.name, "unit_threaded.")) {
28                 module_.unitTest()();
29             }
30         }
31     }
32 
33     return true;
34 }
35 
36 
37 /**
38  * Creates tests cases from the given modules.
39  * If testsToRun is empty, it means run all tests.
40  */
41 TestCase[] createTestCases(in TestData[] testData, in string[] testsToRun = []) {
42     bool[TestCase] tests;
43     foreach(const data; testData) {
44         if(!isWantedTest(data, testsToRun)) continue;
45         auto test = createTestCase(data);
46         if(test !is null) tests[test] = true; //can be null if abtract base class
47     }
48 
49     return tests.keys.sort!((a, b) => a.getPath < b.getPath).array;
50 }
51 
52 
53 private TestCase createTestCase(in TestData testData) {
54     TestCase createImpl() {
55         if(testData.testFunction is null) return cast(TestCase) Object.factory(testData.name);
56         return testData.builtin ? new BuiltinTestCase(testData) : new FunctionTestCase(testData);
57     }
58 
59     auto testCase = createImpl();
60 
61     if(testData.singleThreaded) {
62         // @Serial tests in the same module run sequentially.
63         // A CompositeTestCase is created for each module with at least
64         // one @Serial test and subsequent @Serial tests
65         // appended to it
66         static CompositeTestCase[string] serialComposites;
67 
68         const moduleName = testData.name.splitter(".").
69             array[0 .. $ - 1].
70             reduce!((a, b) => a ~ "." ~ b);
71 
72         if(moduleName !in serialComposites) {
73             serialComposites[moduleName] = new CompositeTestCase;
74         }
75 
76         serialComposites[moduleName] ~= testCase;
77         return serialComposites[moduleName];
78     }
79 
80     if(testData.shouldFail) {
81         return new ShouldFailTestCase(testCase);
82     }
83 
84     assert(testCase !is null || testData.testFunction is null,
85            "Could not create TestCase object for test " ~ testData.name);
86 
87     return testCase;
88 }
89 
90 
91 private bool isWantedTest(in TestData testData, in string[] testsToRun) {
92     if(!testsToRun.length) return !testData.hidden; //all tests except the hidden ones
93     bool matchesExactly(in string t) { return t == testData.name; }
94     bool matchesPackage(in string t) { //runs all tests in package if it matches
95         with(testData) return !hidden && name.length > t.length &&
96                        name.startsWith(t) && name[t.length .. $].canFind(".");
97     }
98     return testsToRun.any!(t => matchesExactly(t) || matchesPackage(t));
99 }
100 
101 
102 unittest {
103     //existing, wanted
104     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"]));
105     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."]));
106     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"]));
107     assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"]));
108     assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"]));
109     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"]));
110     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"]));
111     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"]));
112     assert(isWantedTest(TestData("pass_tests.testEqual"), []));
113     assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"]));
114     assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"),
115                          ["example.tests.pass.io.TestFoo"]));
116     assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), []));
117     assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", null, true /*hidden*/), ["tests.pass"]));
118 }