1 /** 2 Creates test cases from compile-time information. 3 */ 4 module unit_threaded.runner.factory; 5 6 import unit_threaded.from; 7 import unit_threaded.runner.testcase: CompositeTestCase; 8 9 10 private CompositeTestCase[string] serialComposites; 11 12 /** 13 * Creates tests cases from the given modules. 14 * If testsToRun is empty, it means run all tests. 15 */ 16 from!"unit_threaded.runner.testcase".TestCase[] createTestCases( 17 in from!"unit_threaded.runner.reflection".TestData[] testData, 18 in string[] testsToRun = []) 19 { 20 import unit_threaded.runner.testcase: TestCase; 21 import std.algorithm: sort; 22 import std.array: array; 23 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 from!"unit_threaded.runner.testcase".TestCase createTestCase( 37 in from!"unit_threaded.runner.reflection".TestData testData) 38 { 39 import unit_threaded.runner.testcase: TestCase; 40 import std.algorithm: splitter, reduce; 41 import std.array: array; 42 43 TestCase createImpl() { 44 import unit_threaded.runner.testcase: 45 BuiltinTestCase, FunctionTestCase, ShouldFailTestCase, FlakyTestCase; 46 import std.conv: text; 47 48 TestCase testCase; 49 50 if(testData.isTestClass) 51 testCase = cast(TestCase) Object.factory(testData.name); 52 else 53 testCase = testData.builtin 54 ? new BuiltinTestCase(testData) 55 : new FunctionTestCase(testData); 56 57 version(unitThreadedLight) {} 58 else 59 assert(testCase !is null, 60 text("Error creating test case with ", 61 testData.isTestClass ? "test class data: " : "data: ", 62 testData)); 63 64 if(testData.shouldFail) { 65 testCase = new ShouldFailTestCase(testCase, testData.exceptionTypeInfo); 66 } else if(testData.flakyRetries > 0) 67 testCase = new FlakyTestCase(testCase, testData.flakyRetries); 68 69 return testCase; 70 } 71 72 auto testCase = createImpl(); 73 74 if(testData.singleThreaded) { 75 // @Serial tests in the same module run sequentially. 76 // A CompositeTestCase is created for each module with at least 77 // one @Serial test and subsequent @Serial tests 78 // appended to it 79 const moduleName = testData.name.splitter(".") 80 .array[0 .. $ - 1]. 81 reduce!((a, b) => a ~ "." ~ b); 82 83 // create one if not already there 84 if(moduleName !in serialComposites) { 85 serialComposites[moduleName] = new CompositeTestCase; 86 } 87 88 // add the current test to the composite 89 serialComposites[moduleName] ~= testCase; 90 return serialComposites[moduleName]; 91 } 92 93 assert(testCase !is null || testData.testFunction is null, 94 "Could not create TestCase object for test " ~ testData.name); 95 96 return testCase; 97 } 98 99 100 101 bool isWantedTest(in from!"unit_threaded.runner.reflection".TestData testData, 102 in string[] testsToRun) 103 { 104 105 import std.algorithm: filter, all, startsWith, canFind; 106 import std.array: array; 107 108 bool isTag(in string t) { return t.startsWith("@") || t.startsWith("~@"); } 109 110 auto normalToRun = testsToRun.filter!(a => !isTag(a)).array; 111 auto tagsToRun = testsToRun.filter!isTag; 112 113 bool matchesTags(in string tag) { //runs all tests with the specified tags 114 assert(isTag(tag)); 115 return tag[0] == '@' && testData.tags.canFind(tag[1..$]) || 116 (!testData.hidden && tag.startsWith("~@") && !testData.tags.canFind(tag[2..$])); 117 } 118 119 return isWantedNonTagTest(testData, normalToRun) && 120 (tagsToRun.empty || tagsToRun.all!(t => matchesTags(t))); 121 } 122 123 private bool isWantedNonTagTest(in from!"unit_threaded.runner.reflection".TestData testData, 124 in string[] testsToRun) 125 { 126 127 import std.algorithm: any, startsWith, canFind; 128 129 if(!testsToRun.length) return !testData.hidden; // all tests except the hidden ones 130 131 bool matchesExactly(in string t) { 132 return t == testData.getPath; 133 } 134 135 bool matchesPackage(in string t) { //runs all tests in package if it matches 136 with(testData) 137 return !hidden && getPath.length > t.length && 138 getPath.startsWith(t) && getPath[t.length .. $].canFind("."); 139 } 140 141 return testsToRun.any!(a => matchesExactly(a) || matchesPackage(a)); 142 } 143 144 145 unittest { 146 assert(false); 147 }