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