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 }