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 private CompositeTestCase[string] serialComposites;
18 
19 /**
20  * Creates tests cases from the given modules.
21  * If testsToRun is empty, it means run all tests.
22  */
23 TestCase[] createTestCases(in TestData[] testData, in string[] testsToRun = []) {
24     serialComposites = null;
25     bool[TestCase] tests;
26     foreach(const data; testData) {
27         if(!isWantedTest(data, testsToRun)) continue;
28         auto test = createTestCase(data);
29         if(test !is null) tests[test] = true; //can be null if abtract base class
30     }
31 
32     return tests.keys.sort!((a, b) => a.getPath < b.getPath).array;
33 }
34 
35 
36 private TestCase createTestCase(in TestData testData) {
37     TestCase createImpl() {
38         TestCase testCase;
39         if(testData.isTestClass)
40             testCase = cast(TestCase) Object.factory(testData.name);
41         else
42             testCase = testData.builtin ? new BuiltinTestCase(testData) : new FunctionTestCase(testData);
43 
44         return testData.shouldFail ? new ShouldFailTestCase(testCase) : testCase;
45     }
46 
47     auto testCase = createImpl();
48 
49     if(testData.singleThreaded) {
50         // @Serial tests in the same module run sequentially.
51         // A CompositeTestCase is created for each module with at least
52         // one @Serial test and subsequent @Serial tests
53         // appended to it
54         const moduleName = testData.name.splitter(".").
55             array[0 .. $ - 1].
56             reduce!((a, b) => a ~ "." ~ b);
57 
58         // create one if not already there
59         if(moduleName !in serialComposites) {
60             serialComposites[moduleName] = new CompositeTestCase;
61         }
62 
63         // add the current test to the composite
64         serialComposites[moduleName] ~= testCase;
65         return serialComposites[moduleName];
66     }
67 
68     assert(testCase !is null || testData.testFunction is null,
69            "Could not create TestCase object for test " ~ testData.name);
70 
71     return testCase;
72 }
73 
74 
75 
76 private bool isWantedTest(in TestData testData, in string[] testsToRun) {
77     bool isTag(in string t) { return t.startsWith("@") || t.startsWith("~@"); }
78 
79     auto normalToRun = testsToRun.filter!(a => !isTag(a)).array;
80     auto tagsToRun = testsToRun.filter!isTag;
81 
82     bool matchesTags(in string tag) { //runs all tests with the specified tags
83         assert(isTag(tag));
84         return tag[0] == '@' && testData.tags.canFind(tag[1..$]) ||
85             (!testData.hidden && tag.startsWith("~@") && !testData.tags.canFind(tag[2..$]));
86     }
87 
88     return isWantedNonTagTest(testData, normalToRun) &&
89         (tagsToRun.empty || tagsToRun.all!(t => matchesTags(t)));
90 }
91 
92 private bool isWantedNonTagTest(in TestData testData, in string[] testsToRun) {
93     if(!testsToRun.length) return !testData.hidden; //all tests except the hidden ones
94 
95     bool matchesExactly(in string t) {
96         return t == testData.name;
97     }
98 
99     bool matchesPackage(in string t) { //runs all tests in package if it matches
100         with(testData) return !hidden && name.length > t.length &&
101                            name.startsWith(t) && name[t.length .. $].canFind(".");
102     }
103 
104     return testsToRun.any!(a => matchesExactly(a) || matchesPackage(a));
105 }
106 
107 
108 unittest {
109     //existing, wanted
110     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"]));
111     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."]));
112     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"]));
113     assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"]));
114     assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"]));
115     assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"]));
116     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"]));
117     assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"]));
118     assert(isWantedTest(TestData("pass_tests.testEqual"), []));
119     assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"]));
120     assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"),
121                          ["example.tests.pass.io.TestFoo"]));
122     assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), []));
123     assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", null, true /*hidden*/), ["tests.pass"]));
124     assert(!isWantedTest(TestData("", null, false /*hidden*/, false /*shouldFail*/, false /*singleThreaded*/,
125                                   false /*builtin*/, "" /*suffix*/),
126                          ["@foo"]));
127     assert(isWantedTest(TestData("", null, false /*hidden*/, false /*shouldFail*/, false /*singleThreaded*/,
128                                  false /*builtin*/, "" /*suffix*/, ["foo"]),
129                         ["@foo"]));
130 
131     assert(!isWantedTest(TestData("", null, false /*hidden*/, false /*shouldFail*/, false /*singleThreaded*/,
132                                  false /*builtin*/, "" /*suffix*/, ["foo"]),
133                         ["~@foo"]));
134 
135     assert(isWantedTest(TestData("", null, false /*hidden*/, false /*shouldFail*/, false /*singleThreaded*/,
136                                   false /*builtin*/, "" /*suffix*/),
137                          ["~@foo"]));
138 
139     assert(isWantedTest(TestData("", null, false /*hidden*/, false /*shouldFail*/, false /*singleThreaded*/,
140                                  false /*builtin*/, "" /*suffix*/, ["bar"]),
141                          ["~@foo"]));
142 
143     // if hidden, don't run by default
144     assert(!isWantedTest(TestData("", null, true /*hidden*/, false /*shouldFail*/, false /*singleThreaded*/,
145                                   false /*builtin*/, "" /*suffix*/, ["bar"]),
146                         ["~@foo"]));
147 
148 
149 }