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.check; 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 // @SingleThreaded tests in the same module run sequentially. 63 // A CompositeTestCase is created for each module with at least 64 // one @SingleThreaded test and subsequent @SingleThreaded tests 65 // appended to it 66 static CompositeTestCase[string] composites; 67 68 const moduleName = testData.name.splitter("."). 69 array[0 .. $ - 1]. 70 reduce!((a, b) => a ~ "." ~ b); 71 72 if(moduleName !in composites) composites[moduleName] = new CompositeTestCase; 73 composites[moduleName] ~= testCase; 74 return composites[moduleName]; 75 } 76 77 if(testData.shouldFail) { 78 return new ShouldFailTestCase(testCase); 79 } 80 81 assert(testCase !is null || testData.testFunction is null, 82 "Could not create TestCase object for test " ~ testData.name); 83 84 return testCase; 85 } 86 87 88 private bool isWantedTest(in TestData testData, in string[] testsToRun) { 89 if(!testsToRun.length) return !testData.hidden; //all tests except the hidden ones 90 bool matchesExactly(in string t) { return t == testData.name; } 91 bool matchesPackage(in string t) { //runs all tests in package if it matches 92 with(testData) return !hidden && name.length > t.length && 93 name.startsWith(t) && name[t.length .. $].canFind("."); 94 } 95 return testsToRun.any!(t => matchesExactly(t) || matchesPackage(t)); 96 } 97 98 99 unittest { 100 //existing, wanted 101 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"])); 102 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."])); 103 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"])); 104 assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"])); 105 assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"])); 106 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"])); 107 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"])); 108 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"])); 109 assert(isWantedTest(TestData("pass_tests.testEqual"), [])); 110 assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"])); 111 assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"), 112 ["example.tests.pass.io.TestFoo"])); 113 assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), [])); 114 assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", true /*hidden*/), ["tests.pass"])); 115 }