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