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 }