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 * Replace the D runtime's normal unittest block tester with our own 18 */ 19 shared static this() { 20 Runtime.moduleUnitTester = &moduleUnitTester; 21 } 22 23 private bool moduleUnitTester() { 24 //this is so unit-threaded's own tests run 25 foreach(module_; ModuleInfo) { 26 if(module_ && module_.unitTest) { 27 if(startsWith(module_.name, "unit_threaded.")) { 28 module_.unitTest()(); 29 } 30 } 31 } 32 33 return true; 34 } 35 36 37 /** 38 * Creates tests cases from the given modules. 39 * If testsToRun is empty, it means run all tests. 40 */ 41 TestCase[] createTestCases(in TestData[] testData, in string[] testsToRun = []) { 42 bool[TestCase] tests; 43 foreach(const data; testData) { 44 if(!isWantedTest(data, testsToRun)) continue; 45 auto test = createTestCase(data); 46 if(test !is null) tests[test] = true; //can be null if abtract base class 47 } 48 49 return tests.keys.sort!((a, b) => a.getPath < b.getPath).array; 50 } 51 52 53 private TestCase createTestCase(in TestData testData) { 54 TestCase createImpl() { 55 if(testData.testFunction is null) return cast(TestCase) Object.factory(testData.name); 56 return testData.builtin ? new BuiltinTestCase(testData) : new FunctionTestCase(testData); 57 } 58 59 auto testCase = createImpl(); 60 61 if(testData.singleThreaded) { 62 // @Serial tests in the same module run sequentially. 63 // A CompositeTestCase is created for each module with at least 64 // one @Serial test and subsequent @Serial tests 65 // appended to it 66 static CompositeTestCase[string] serialComposites; 67 68 const moduleName = testData.name.splitter("."). 69 array[0 .. $ - 1]. 70 reduce!((a, b) => a ~ "." ~ b); 71 72 if(moduleName !in serialComposites) { 73 serialComposites[moduleName] = new CompositeTestCase; 74 } 75 76 serialComposites[moduleName] ~= testCase; 77 return serialComposites[moduleName]; 78 } 79 80 if(testData.shouldFail) { 81 return new ShouldFailTestCase(testCase); 82 } 83 84 assert(testCase !is null || testData.testFunction is null, 85 "Could not create TestCase object for test " ~ testData.name); 86 87 return testCase; 88 } 89 90 91 private bool isWantedTest(in TestData testData, in string[] testsToRun) { 92 if(!testsToRun.length) return !testData.hidden; //all tests except the hidden ones 93 bool matchesExactly(in string t) { return t == testData.name; } 94 bool matchesPackage(in string t) { //runs all tests in package if it matches 95 with(testData) return !hidden && name.length > t.length && 96 name.startsWith(t) && name[t.length .. $].canFind("."); 97 } 98 return testsToRun.any!(t => matchesExactly(t) || matchesPackage(t)); 99 } 100 101 102 unittest { 103 //existing, wanted 104 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"])); 105 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."])); 106 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"])); 107 assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"])); 108 assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"])); 109 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"])); 110 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"])); 111 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"])); 112 assert(isWantedTest(TestData("pass_tests.testEqual"), [])); 113 assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"])); 114 assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"), 115 ["example.tests.pass.io.TestFoo"])); 116 assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), [])); 117 assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", null, true /*hidden*/), ["tests.pass"])); 118 }