1 module unit_threaded.factory; 2 3 import unit_threaded.testcase; 4 import unit_threaded.list; 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.exception; 12 import std.algorithm; 13 import std.array; 14 import std.string; 15 import core.runtime; 16 17 /** 18 * Replace the D runtime's normal unittest block tester with our own 19 */ 20 shared static this() { 21 Runtime.moduleUnitTester = &moduleUnitTester; 22 } 23 24 /** 25 * Creates tests cases from the given modules. 26 * If testsToRun is empty, it means run all tests. 27 */ 28 auto createTests(MODULES...)(in string[] testsToRun = []) if(MODULES.length > 0) { 29 bool[TestCase] tests; 30 foreach(data; getAllTestCases!MODULES()) { 31 if(!isWantedTest(data, testsToRun)) continue; 32 auto test = createTestCase(data); 33 if(test !is null) tests[test] = true; //can be null if abtract base class 34 } 35 36 return tests.keys.sort!((a, b) => a.getPath < b.getPath).array; 37 } 38 39 40 private TestCase createTestCase(TestData data) { 41 TestCase createImpl(TestData data) { 42 if(data.test is null) return cast(TestCase) Object.factory(data.name); 43 return data.builtin ? new BuiltinTestCase(data) : new FunctionTestCase(data); 44 } 45 46 if(data.singleThreaded) { 47 static CompositeTestCase[string] composites; 48 const moduleName = getModuleName(data.name); 49 if(moduleName !in composites) composites[moduleName] = new CompositeTestCase; 50 composites[moduleName] ~= createImpl(data); 51 return composites[moduleName]; 52 } 53 54 if(data.shouldFail) { 55 return new ShouldFailTestCase(createImpl(data)); 56 } 57 58 auto testCase = createImpl(data); 59 if(data.test !is null) { 60 assert(testCase !is null, "Could not create TestCase object for test " ~ data.name); 61 } 62 63 return testCase; 64 } 65 66 private string getModuleName(in string name) { 67 return name.splitter(".").array[0 .. $ -1].reduce!((a, b) => a ~ "." ~ b); 68 } 69 70 71 private bool isWantedTest(in TestData data, in string[] testsToRun) { 72 if(!testsToRun.length) return !data.hidden; //all tests except the hidden ones 73 bool matchesExactly(in string t) { return t == data.name; } 74 bool matchesPackage(in string t) { //runs all tests in package if it matches 75 with(data) return !hidden && name.length > t.length && 76 name.startsWith(t) && name[t.length .. $].canFind("."); 77 } 78 return testsToRun.any!(t => matchesExactly(t) || matchesPackage(t)); 79 } 80 81 82 package auto getAllTestCases(MODULES...)() { 83 auto getAllTestsWithFunc(string expr, MODULES...)() pure nothrow { 84 //tests is whatever type expr returns 85 ReturnType!(mixin(expr ~ q{!(MODULES[0])})) tests; 86 foreach(mod; TypeTuple!MODULES) { 87 tests ~= mixin(expr ~ q{!mod()}); //e.g. tests ~= getTestClasses!mod 88 } 89 return assumeUnique(tests); 90 } 91 92 return getAllTestsWithFunc!(q{getTestClasses}, MODULES) ~ 93 getAllTestsWithFunc!(q{getTestFunctions}, MODULES) ~ 94 getAllTestsWithFunc!(q{getBuiltinTests}, MODULES); 95 } 96 97 98 private bool moduleUnitTester() { 99 //this is so unit-threaded's own tests run 100 foreach(mod; ModuleInfo) { 101 if(mod && mod.unitTest) { 102 if(startsWith(mod.name, "unit_threaded.")) { 103 mod.unitTest()(); 104 } 105 } 106 } 107 108 return true; 109 } 110 111 112 unittest { 113 //existing, wanted 114 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests"])); 115 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests."])); 116 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribe"])); 117 assert(!isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server.testSubscribeWithMessage"])); 118 assert(!isWantedTest(TestData("tests.stream.testMqttInTwoPackets"), ["tests.server"])); 119 assert(isWantedTest(TestData("tests.server.testSubscribe"), ["tests.server"])); 120 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests"])); 121 assert(isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.testEqual"])); 122 assert(isWantedTest(TestData("pass_tests.testEqual"), [])); 123 assert(!isWantedTest(TestData("pass_tests.testEqual"), ["pass_tests.foo"])); 124 assert(!isWantedTest(TestData("example.tests.pass.normal.unittest"), 125 ["example.tests.pass.io.TestFoo"])); 126 assert(isWantedTest(TestData("example.tests.pass.normal.unittest"), [])); 127 assert(!isWantedTest(TestData("tests.pass.attributes.testHidden", true /*hidden*/), ["tests.pass"])); 128 } 129 130 131 132 unittest { 133 import unit_threaded.asserts; 134 assertEqual(getModuleName("tests.fail.composite.Test1"), "tests.fail.composite"); 135 }