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 private bool isWantedTest(in TestData testData, in string[] testsToRun) { 76 if(!testsToRun.length) return !testData.hidden; //all tests except the hidden ones 77 bool matchesExactly(in string t) { return t == testData.name; } 78 bool matchesPackage(in string t) { //runs all tests in package if it matches 79 with(testData) return !hidden && name.length > t.length && 80 name.startsWith(t) && name[t.length .. $].canFind("."); 81 } 82 return testsToRun.any!(t => matchesExactly(t) || matchesPackage(t)); 83 } 84 85 86 unittest { 87 //existing, wanted 88 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"])); 89 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."])); 90 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"])); 91 assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"])); 92 assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"])); 93 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"])); 94 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"])); 95 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"])); 96 assert(isWantedTest(TestData("pass_tests.testEqual"), [])); 97 assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"])); 98 assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"), 99 ["example.tests.pass.io.TestFoo"])); 100 assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), [])); 101 assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", null, true /*hidden*/), ["tests.pass"])); 102 }