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